# mitsubishi-label-converter-py A Python CLI tool that converts Mitsubishi GX Works3 global label XML exports (`Global.xml`, PLCopen TC6 format, typically UTF-16 LE encoded) **to Excel** and **back to XML** with a lossless round-trip. Each `` section group in the source file becomes its own worksheet: sections sharing the same name (e.g. a plain and a retain ``Global`` list) are merged into one sheet, with the per-variable retain flag in the ``Retain`` column. The reverse conversion splits the rows back into the original sections. Every `` becomes one row with: | Column | Content | |-----------|-------------------------------------------------------------------------| | Name | The variable's `name` attribute | | Address | The variable's `address` attribute (empty when unassigned) | | Type | The declared type (see below) | | Comment | The variable comment from `variableComments` addData (if present) | | Line | Original source line number from `variableLineNumber` addData | | Retain | `TRUE` when the variable belongs to a `retain="true"` globalVars section | **Structured variables** (derived types like `ST_Din_STA`) are expanded: each member with a device address gets its own row directly under the variable, using a dotted name (`PLC_FEL_STA.Value -> D0.0`). The addresses come from `variableStructDeviceAssignment` addData. **Member types** — the Global XML export carries only member names and addresses; member *types* live in a separate SDT export. Pass it via `--sdt` to populate the Type column of member rows (`BOOL`, `WORD`, `STRING[16]`, `ARRAY[1..4] OF INT`, nested struct names, ...). Without the flag, member Type cells stay empty. A **`Meta`** worksheet records document metadata (fileHeader, contentHeader, PLC type) plus the sheet-to-section mapping (names and `varListProperties` timestamps) so the reverse conversion is lossless. Retain information lives in the `Retain` column, not in Meta. ## Type rendering - Primitive types (``, ``, ``, ``, ...) are written as-is. - `` is written as `STRING[12]`. - Structured/derived types (``) are written using the derived name, e.g. `FB_Din`. The reverse conversion recognizes primitives from the standard IEC set; everything else is emitted as a `` reference again. ## Validation report The `report` subcommand runs consistency checks on an XML export or an Excel workbook and prints the findings grouped by severity: | Severity | Check | Code | |----------|-------|------| | error | duplicate variable names | `NAME_DUPLICATE` | | error | identical device address used by several variables | `ADDR_DUPLICATE` | | warning | bit address inside another variable's word range | `ADDR_BIT_IN_WORD` | | warning | word ranges of two variables overlap (types sized in 16-bit registers; `STRING[n]` occupies `(n+2)/2` registers) | `ADDR_OVERLAP` | | warning | address in an unrecognized format | `ADDR_FORMAT` | | warning | struct member not defined in the SDT struct | `MEMBER_UNKNOWN` | | info | struct members without a device address | `MEMBER_UNASSIGNED` | | info | structured variable without any device assignment | `STRUCT_UNASSIGNED` | | info | derived type missing from the SDT file (aggregated per type) | `TYPE_UNKNOWN` | Members of the same structured variable may legitimately share word registers, so only cross-variable collisions are reported. The command exits with code 1 when errors are found (0 for warnings/info only), so it can gate CI pipelines. Address parsing handles word-level (`D450`), bit-level (`D6005.0`), and hexadecimal bit suffixes (`D201.A` = bit 10). ## Installation ```bash # from the project root pip install -e .[dev] ``` ## Usage ```bash # XML -> Excel: output is written next to the source file (Global.xlsx) mitsubishi-label-converter to-xlsx assets/Global.xml # with member types from an SDT (structured data type) export mitsubishi-label-converter to-xlsx assets/Global.xml --sdt assets/SDT.xml # explicit output path mitsubishi-label-converter to-xlsx assets/Global.xml -o labels.xlsx # Excel -> XML: regenerate a GX Works3-compatible XML file mitsubishi-label-converter to-xml assets/Global.xlsx -o output/Global.xml # validate labels (XML or Excel input); exit code 1 on errors (CI-friendly) mitsubishi-label-converter report assets/Global.xml --sdt assets/SDT.xml # save the report to a file instead of stdout mitsubishi-label-converter report assets/Global.xlsx -o output/validation.txt # overwrite existing outputs mitsubishi-label-converter to-xlsx assets/Global.xml --force ``` The round-trip is lossless: parsing the regenerated XML yields exactly the same sections, variables, members, comments, line numbers, retain flags, and metadata as the original file. The `to-xml` conversion regroups the rows of a merged sheet by their `Retain` flag, restoring the original section split (plain first, retain second). Member types from the SDT file are Excel-only decoration and are intentionally not written to the XML (the XML format stores member types in its `dataTypes` section, not in device assignments). ## Development ```bash pip install -e .[dev] pytest ``` ## TODO / Roadmap Done: - [x] **Expose struct members with addresses in Excel** — dotted member rows inline under each structured variable (189 structured variables -> 1,220 member rows in the sample file). - [x] **Member types from SDT export** — `--sdt` flag populates the Type column of member rows from a structured data type XML export; without it the workbook is unchanged. - [x] **Merged sheets + Retain column** — sections sharing a name merge into one worksheet; the per-variable `Retain` column marks retain variables, and `to-xml` regroups rows by flag to restore the original sections. - [x] **Validation report** — `report` subcommand checks duplicate names/addresses, address overlaps (bit-in-word, word ranges), struct member consistency, and unassigned structs; works on XML and Excel input; CI-friendly exit codes. - [x] **Excel -> XML round-trip conversion** — `to-xml` subcommand; `Meta` sheet carries document metadata and sheet mapping; verified lossless against the real 290-variable file. - [x] **CLI subcommands** — `to-xlsx` / `to-xml` with `-o/--output` and `--force` flags. Planned: 1. **`--all-members`: expose SDT members without device addresses (requested)** GX Works3 only exports struct members that carry a device address; a structured variable without any assignment (e.g. `GP01_RUNTIME_PAR` of type `ST_Runtime_PAR`) has no member rows at all — its internal members (`StartsTot`, `RunTimeTot`, ...) exist only in the SDT file. Plan: - `expand_missing_members()` in `sdt_parser.py`: append every SDT-defined member missing from the device assignment as an address-less row (Type populated, Retain inherited from the parent variable). - `--all-members` flag on `to-xlsx` (requires `--sdt`). - `xml_writer` must skip address-less `` entries so `to-xml` stays lossless (the original XML has no such members). - Optional follow-up: recursive expansion for nested struct members (`Parent.Sub.uHour`, e.g. `stError_Time -> M+FX5UCPU_Error_Time`). - Verification: fabricated fixture (`tests/test_all_members.py`) with (a) a partially assigned struct, (b) a struct variable with no assignment at all, (c) both in the retain section. 2. **CLI conveniences** - `--list` flag: print the parsed label tables to the terminal. - `--sheet-per-type` flag: split variables into one worksheet per type. - `--filter ` flag: convert only variables matching a name pattern. - `--round-trip` flag: convert back and forth and verify losslessness automatically. 3. **Multiple input files** — accept several XML files in one invocation, each producing its own workbook (e.g. when a project contains several resources/POUs). 4. **CSV output** — `--format csv` to emit one CSV file per globalVars section, for tools that cannot read `.xlsx`. 5. **GUI drag & drop (long-term)** — a small GUI (e.g. tkinter or a web UI) for drag-and-drop conversion for non-CLI users.