Bulk Editor for FactoryTalk Batch Recipes
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
Features
-
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.
Requirements
- Python 3.14 (in theory 3.10+)
- Dependencies
- For xml and excel work:
- lxml
- openpyxl
- For CLI:
- typer
- For xml and excel work:
Installation
Installation
Using pip
-
Clone this repo.
-
Install dependencies:
pip ftbatch-bulk-edit -
Run:
ftbatch-bulk-edit xml2excel --xml yourRecipe.pxml --excel out.xlsx
Using uv as the Package Manager
uv sync --python 3.14
uv run python app/main.py xml2excel --xml yourRecipe.pxml --excel out.xlsx
Using the example in the repo
uv run python app/main.py --progress xml2excel --xml tests/examples/LNP/P_LNP.PXML --excel tests/examples/converted-outputs/output.xlsx
Using the Justfile (Recommended)
We 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 artifacts
Usage Overview
Export XML to Excel (xml2excel)
python 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. |
Import Excel to XML (excel2xml)
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. |
Global Options
| 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. |
Debug Mode
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.xlsx
By default, progress bars are enabled only for interactive terminals.
Flow Chart
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
Example Excel Output
| 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 |
Example XML Schema Snippet
<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>
Preservation Rules & Edge Cases
- 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]
Troubleshooting
-
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
Metadata
Release files for ftbatch-bulk-edit 0.2.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ftbatch_bulk_edit-0.2.6.tar.gz | 212.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ftbatch_bulk_edit-0.2.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 236.9 kB
Release files / ftbatch_bulk_edit-0.2.6.tar.gz
| Download URL | ftbatch_bulk_edit-0.2.6.tar.gz |
|---|---|
| Size | 212.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
74bec1619d909aca9349f042baa8c24d0bb25c08f73dcde9072204ee4f0b3d50
|
|
BLAKE2b-256 checksum How to use checksums |
f497abc916ccf30d3ad71858709885520321de9addf38022aaab6cd8c309429c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / ftbatch_bulk_edit-0.2.6-py3-none-any.whl
| Download URL | ftbatch_bulk_edit-0.2.6-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
03290edde9f220f6962a88454d911d136ff1c06ce8758429f4c043a39dd9291e
|
|
BLAKE2b-256 checksum How to use checksums |
e3be1711289baeba0ee737d2dc4e67d49dfd2a89fedadf980f3bdb784f74d691
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|