"""Document-level helpers for a Sedona app: the packed 'meta' property and component paths. Pure XML and bit work, deliberately free of Tk so the rules stay testable headless. """ import copy import xml.etree.ElementTree as ET def snapshot_children(element): """Copies an element's children and leading text, so it can be put back wholesale. Used for the block, which is short enough that copying it whole is easier to trust than tracking which attribute moved and which entry was inserted where. """ return [copy.deepcopy(child) for child in element], element.text def restore_children(element, snapshot): """Puts back what snapshot_children captured, whitespace and all.""" children, text = snapshot for child in list(element): element.remove(child) element.text = text for child in children: element.append(copy.deepcopy(child)) def read_meta_property(meta_str): """Parses 32-bit meta property integer fields via structured byte extraction rules.""" if meta_str is None or not meta_str: return 0, 0, 0, True, False, False, False, 0 try: if meta_str.startswith("0x") or meta_str.startswith("0X"): meta_val = int(meta_str, 16) else: meta_val = int(meta_str) raw_x = (meta_val >> 24) & 0xFF raw_y = (meta_val >> 16) & 0xFF reserved = (meta_val >> 8) & 0xFF user_group_byte = meta_val & 0xFF group1 = bool(user_group_byte & 0x01) group2 = bool(user_group_byte & 0x02) group3 = bool(user_group_byte & 0x04) group4 = bool(user_group_byte & 0x08) user_group_reserved = (user_group_byte >> 4) & 0x0F return raw_x, raw_y, reserved, group1, group2, group3, group4, user_group_reserved except Exception: return 0, 0, 0, True, False, False, False, 0 def update_meta_property(element, meta_str, raw_x, raw_y): """Re-encodes coordinates back into meta, completely preserving existing lower and consecutive bytes.""" _, _, reserved, g1, g2, g3, g4, ug_res = read_meta_property(meta_str) user_group_byte = 0 if g1: user_group_byte |= 0x01 if g2: user_group_byte |= 0x02 if g3: user_group_byte |= 0x04 if g4: user_group_byte |= 0x08 user_group_byte |= (ug_res & 0x0F) << 4 new_val = ( ((raw_x & 0xFF) << 24) | ((raw_y & 0xFF) << 16) | ((reserved & 0xFF) << 8) | (user_group_byte & 0xFF) ) meta_prop = element.find("prop[@name='meta']") if meta_prop is None: meta_prop = ET.SubElement(element, "prop", name="meta") meta_prop.set("val", str(new_val)) def find_parent_element(root_elem, child_to_find): """Traverses the XML DOM tree recursively to find the parent element of a given element.""" for parent in root_elem.iter(): if child_to_find in list(parent): return parent return None def get_component_path(element, root_elem): """Computes the absolute path of a component within the application tree hierarchy.""" path_parts = [] curr = element while curr is not None and curr.tag == "comp": path_parts.append(curr.get("name", "")) curr = find_parent_element(root_elem, curr) path_parts.reverse() return "/" + "/".join(path_parts)