A command-line tool that lets you bulk-edit Rockwell FactoryTalk Batch S88 recipes (.pxml, .uxml, and .oxml) using Excel. You can export your recipes into a spreadsheet, make bulk changes to parameters and formula-values, then import the edits back to produce updated XML files. It will presere element order, empty tags, and original structure.
Note
Useful for large recipes with many defered parameters
Table of Contents
-
Bulk Editing in Excel
- Extract top-level
<Parameter>and step-level<FormulaValue>into one sheet per file. - Edit hundreds of tags in your favorite spreadsheet tool.
- Extract top-level
-
Preserve Order & Empties
- Maintains the original element order.
- Keeps empty tags (e.g.
<EngineeringUnits/>,<String/>). - Retains existing
<FormulaValueLimit>blocks unless you remove them.
-
Recursive Child Parsing
- A
.pxmlmay reference.uxml; a.uxmlmay reference.oxmlvia<StepRecipeID>. - Exports all related files into one workbook.
- A
-
Selective Create/Update/Delete
- Update if a row’s
FullPathmatches an existing node. - Delete if you remove a row entirely.
- Create if you add a new
FullPathrow (with one valid data-type column).
- Update if a row’s
-
Detailed Logging
- Console keeps high-signal warnings/summaries with Rich formatting.
- DEBUG-level detail (with
--debug) is written tobatch_bulk_editor.log.
-
ParamExpression support
- For FormulaValues you can now supply a real‐time expression.
- In Excel, fill the appropriate data‐type column (Real/Integer/String) with the literal
ParamExpression, and put the actual expression into the new ParamExpression column (appearing right after Defer). - On import, the tool will generate a
<ParamExpression/>tag, insert your formula into<Real>(or<Integer>, etc.), and preserve correct XML ordering.
- Python 3.14 (in theory 3.10+)
- Dependencies
- For xml and excel work:
- lxml
- openpyxl
- For CLI:
- typer
- For xml and excel work:
Installation
-
Install:
pip install ftbatch-bulk-edit
-
Run:
ftbatch-bulk-edit xml2excel --xml yourRecipe.pxml --excel out.xlsx
uv sync --python 3.14
uv pip install ftbatch-bulk-edit
uv run python ftbatch-bulk-edit xml2excel --xml yourRecipe.pxml --excel out.xlsxuv run ftbatch-bulk-edit --progress xml2excel --xml tests/examples/LNP/P_LNP.PXML --excel tests/examples/converted-outputs/output.xlsxWe provide a justfile to automate setup, testing, linting, coverage, and executable builds:
just help # list recipes
just sync # install deps for Python 3.14
just ci-check # CI parity (lint + tests + coverage threshold)
just test # run tests
just lint # run Ruff lint checks
just cov # run tests with coverage threshold
just package # build sdist/wheel + twine check + smoke tests
UV_PUBLISH_TOKEN=... just publish-testpypi-dry # TestPyPI auth/check dry-run
UV_PUBLISH_TOKEN=... just publish-testpypi # publish to TestPyPI
just verify-install-testpypi # install from TestPyPI + smoke test
just bump-dry-run # print next semver from unreleased commits
just changelog-dry-run # preview unreleased section with bumped version
just release-dry-run # show current/next version + release notes preview
just changelog # write full CHANGELOG.md from git tags
just version-info # generate build/version_info.txt
just build # build one-file executable with PyInstaller
just clean # remove generated artifactspython app/main.py [GLOBAL_OPTIONS] xml2excel --xml PATH_TO_XML --excel OUTPUT_XLSX| Argument | Description |
|---|---|
--xml |
Parent file (.pxml, .uxml, or .oxml). Children loaded via <StepRecipeID>. |
--excel |
Destination Excel workbook. |
python app/main.py [GLOBAL_OPTIONS] excel2xml --xml PATH_TO_XML --excel EDITED_XLSX| Argument | Description |
|---|---|
--xml |
Same parent file used for export. |
--excel |
Edited Excel workbook. |
| Option | Description |
|---|---|
--debug |
Write DEBUG logs to batch_bulk_editor.log. |
--no-progress |
Disable progress bars |
--version |
Show CLI version and exit. |
--help |
Show command help. |
Add --debug before the subcommand to capture detailed logs:
python app/main.py --debug xml2excel --xml myRecipe.pxml --excel out.xlsx- Creates/appends
batch_bulk_editor.logat DEBUG level. - Console logs are rendered with Rich coloring.
- Repeated warning patterns are summarized in console output while debug file logs remain detailed.
Use --no-progress before the subcommand to control progress bars:
python app/main.py --no-progress excel2xml --xml myRecipe.pxml --excel edits.xlsxBy default, progress bars are enabled only for interactive terminals.
flowchart LR
subgraph XML2Excel
A1([Start]) --> A2{XML exists?}
A2 -- No --> A3([Error & Exit])
A2 -- Yes --> A4[Parse parent & children]
A4 --> A5[Extract nodes → row dicts]
A5 --> A6[Write Excel workbook]
A6 --> A7([Done])
end
subgraph Excel2XML
B1([Start]) --> B2{XML exists?}
B2 -- No --> B3([Error & Exit])
B2 -- Yes --> B4[Load original XML → RecipeTree]
B4 --> B5[Read Excel rows]
B5 --> B6{"Row = existing/new/missing?"}
B6 -- existing --> B7[update node]
B6 -- new --> B8[create node]
B6 -- missing --> B9[delete node]
B7 --> B10[Reorder & write XML]
B8 --> B10
B9 --> B10
B10 --> B11([Done])
end
XML2Excel -.-> Excel2XML
| FullPath | TagType | Name | Real | Integer | String | EnumerationSet | EnumerationMember | Defer | FormulaValueLimit_Verification | … |
|---|---|---|---|---|---|---|---|---|---|---|
| TEST/Parameter[XFER5_TARGET_CONC_DB] | Parameter | XFER5_TARGET_CONC_DB | 0 | |||||||
| TEST/Steps/Step[ACQ_REL:1]/FormulaValue[X_R_END_PHASE_PROMPT] | FormulaValue | X_R_END_PHASE_PROMPT | N_DISABLEENABLE | DISABLE | No_Limits | |||||
| TEST/Steps/Step[ACQ_REL:1]/FormulaValue[X_R_END_TYPE] | FormulaValue | X_R_END_TYPE | N_ENDTYPE | AQREL_END_TYPE | No_Limits |
<xs:element name="Parameter">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0"/>
<xs:element name="ERPAlias" minOccurs="0"/>
<xs:element name="PLCReference" type="xs:unsignedByte" minOccurs="0"/>
<xs:element name="Real" type="xs:unsignedShort" minOccurs="0"/>
<xs:element name="High" type="xs:unsignedShort" minOccurs="0"/>
<xs:element name="Low" type="xs:unsignedByte" minOccurs="0"/>
<xs:element name="EngineeringUnits" type="xs:string" minOccurs="0"/>
<xs:element name="Scale" type="xs:boolean" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FormulaValue">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0"/>
<xs:element name="Display" type="xs:boolean" minOccurs="0"/>
<xs:element name="Value" minOccurs="0"/>
<xs:element name="Defer" type="xs:string" minOccurs="0"/>
<xs:element name="Real" type="xs:decimal" minOccurs="0"/>
<xs:element name="EngineeringUnits" type="xs:string" minOccurs="0"/>
<xs:element name="FormulaValueLimit" minOccurs="0">
<xs:complexType>
<xs:attribute name="Verification" type="xs:string" use="optional"/>
<xs:sequence>
<xs:element name="LowValue" type="xs:decimal" minOccurs="0"/>
<xs:element name="HighValue" type="xs:decimal" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>- Empty Tags: If a sub-element existed (even empty), it remains.
- Create FormulaValueLimit only if
FormulaValueLimit_*columns are non-blank or it existed originally. - Type Enforcement: Exactly one of Real, Integer, String, or EnumerationSet per row.
- Defer Validation: A
<FormulaValue>’sDefermust match a parameter name in the same sheet.- Example output:
[30-May-2025 14:49:35] ERROR: CP_GMP_PROCEDURE.PXML!Row924: defer target 'DSTFP_TEMP_PROC2_TEMP_STATE_CMD' not found for CP_GMP_PROCEDURE/Steps/Step[UP_DSTFP_PROCESS:1]/FormulaValue[TEMP_PROC2_TEMP_STATE_CMD]-
No Sheet Found
- Sheet name must match the XML filename (
TEST.pxml→ sheet “TEST.pxml”).
- Sheet name must match the XML filename (
-
Missing Data-Type
- Every Parameter or FormulaValue row must have one and only one data-type column.
-
Need More Logs?
- Add
--debugto capture detailed steps inbatch_bulk_editor.log.
- Add

