Files
mitsubishi-label-converter-py/README.md
T
2026-10-02 06:10:55 +02:00

176 lines
8.3 KiB
Markdown

# 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 `<globalVars>` 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 `<variable>` 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 (`<INT/>`, `<BOOL/>`, `<REAL/>`, `<WORD/>`, ...) are written as-is.
- `<string length="12"/>` is written as `STRING[12]`.
- Structured/derived types (`<derived name="FB_Din"/>`) 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 `<derived>` 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 `<member>` 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 <glob>` 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.