Py_sax2sab/config.py
arda.aydin@ontrol.com.tr ec4019ea96 Convert both directions, and read sedona home from system.properties
Adds the reverse conversion (sab -> sax) and makes the tools usable without
remembering paths on the command line.

- sax.py: decoder ported from OfflineApp.decodeAppBinary + encodeAppXml.
  Rebuilds the tree from the firstChild/nextSibling chain and writes XML the
  way XWriter does. Reproduces java.lang.Float.toString, and rounds manifest
  defaults to f4 before comparing them - otherwise props equal to their
  default (0.1) get written where sedonac omits them.
- sab2sax.py: CLI dispatching on the input extension, like sedonac. sab.py
  still runs and hands off to it.
- config.py: sedona.home lives in system.properties. If it is missing or has
  no manifests/, a folder chooser opens and the answer is written back. File
  choosers for the input, save dialogs for the output; the default output name
  carries a timestamp so a run never overwrites the previous one.
- verify.py: checks both directions plus a sab -> sax -> sab round trip, and
  points at testprogram/ where the test files actually live.

All three checks pass byte-identical against the checked-in sedonac output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:51:04 +03:00

181 lines
5.9 KiB
Python

"""Configuration: sedona home lives in `system.properties` next to this file.
Same key sedonac itself uses, but written as a plain Windows path:
sedona.home=C:\\path\\to\\sedona
We only *read* this file, so there is no reason to escape anything. Java's
Properties (what sedonac uses) treats `\\` as an escape, so its own files double
them up -- `\\\\path\\\\to`. Both parse here: `\\\\` collapses to `\\`, and every
other backslash is kept literally, so `C:\\niagara` does not turn into a newline.
If the key is missing, or the directory no longer holds `manifests/`, a folder
chooser is shown and the answer is written back to `system.properties`.
"""
import os, sys
HERE = os.path.dirname(os.path.abspath(__file__))
PROPS = os.path.join(HERE, "system.properties")
KEY = "sedona.home"
# ---------- .properties ----------
def _unescape(v):
"""Collapse `\\\\` to `\\`; leave any other backslash alone.
Accepts both our plain paths and java-escaped ones, and never mistakes a
path segment for an escape (`C:\\niagara` stays `C:\\niagara`).
"""
out, i = [], 0
while i < len(v):
if v[i] == "\\" and i + 1 < len(v) and v[i + 1] == "\\":
i += 1
out.append(v[i])
i += 1
return "".join(out)
def read_props(path=PROPS):
props = {}
if not os.path.exists(path):
return props
with open(path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
if not line or line[0] in "#!" or "=" not in line:
continue
k, v = line.split("=", 1)
props[k.strip()] = _unescape(v.strip()) # last wins
return props
def write_prop(key, value, path=PROPS):
"""Set `key`, keeping every other line of the file intact."""
lines = []
if os.path.exists(path):
with open(path, "r", encoding="utf-8", errors="replace") as f:
lines = f.read().splitlines()
new = "%s=%s" % (key, value)
done = False
for i, line in enumerate(lines):
s = line.strip()
if s and s[0] not in "#!" and "=" in s and s.split("=", 1)[0].strip() == key:
lines[i] = new if not done else "#" + line # comment out later dupes
done = True
if not done:
lines.append(new)
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
# ---------- dialogs ----------
def _tk():
"""A hidden root window, or None when no GUI is available."""
try:
import tkinter as tk
root = tk.Tk()
root.withdraw()
return root
except Exception:
return None
def ask_directory(title, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.askdirectory(title=title, initialdir=initial or HERE) or None
finally:
root.destroy()
def ask_open_file(title, filetypes, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.askopenfilename(title=title, filetypes=filetypes,
initialdir=initial or HERE) or None
finally:
root.destroy()
def ask_save_file(title, filetypes, defaultextension, initialfile=None, initial=None):
root = _tk()
if root is None:
return None
try:
from tkinter import filedialog
return filedialog.asksaveasfilename(title=title, filetypes=filetypes,
defaultextension=defaultextension,
initialfile=initialfile,
initialdir=initial or HERE) or None
finally:
root.destroy()
# ---------- api ----------
def is_sedona_home(path):
return bool(path) and os.path.isdir(os.path.join(path, "manifests"))
def get_sedona_home(interactive=True):
"""Path from system.properties, prompting (and saving) when unusable."""
home = read_props().get(KEY)
if is_sedona_home(home):
return home
if not interactive:
raise SystemExit("%s not set (or has no manifests/) in %s" % (KEY, PROPS))
why = "not set in" if not home else "invalid (%s), update" % home
print("sedona.home %s %s -- please choose the sedona installation folder" % (why, PROPS))
while True:
picked = ask_directory("Select Sedona home (the folder containing manifests/)",
home if home and os.path.isdir(home) else None)
if not picked:
raise SystemExit("no sedona home selected")
picked = os.path.normpath(picked)
if is_sedona_home(picked):
write_prop(KEY, picked)
print("saved %s=%s to %s" % (KEY, picked, PROPS))
return picked
print(" %s has no manifests/ subfolder -- try again" % picked)
SAX = ("Sedona app XML", "*.sax")
SAB = ("Sedona app binary", "*.sab")
ANY = ("All files", "*.*")
def choose_input_file(initial=None):
"""Pick the file to convert -- direction follows its extension."""
return ask_open_file("Select the .sax or .sab application file to convert",
[("Sedona app (*.sax, *.sab)", "*.sax *.sab"), SAX, SAB, ANY],
initial)
def choose_sax_file(initial=None):
return ask_open_file("Select the .sax application file", [SAX, ANY], initial)
def choose_sab_file(initialfile=None, initial=None):
"""Save-as for the .sab produced from a .sax."""
return ask_save_file("Save the .sab application file", [SAB, ANY],
".sab", initialfile, initial)
def choose_sax_output(initialfile=None, initial=None):
"""Save-as for the .sax produced from a .sab."""
return ask_save_file("Save the .sax application file", [SAX, ANY],
".sax", initialfile, initial)
if __name__ == "__main__":
print(get_sedona_home())