Metadata-Version: 2.4
Name: ftbatch-bulk-edit
Version: 0.2.6
Summary: FactoryTalk Batch Recipe Bulk Editor
Keywords: FactoryTalk Batch,ftbatch,ias88,defer,bulk edit
Author-Email: Marcelo Amorim <marc.amorim6@gmail.com>, Dan Nguyen <dan.nguyen@wmeng.com>
Maintainer-Email: Marcelo Amorim <marc.amorim6@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Text Processing :: Markup :: XML
Classifier: Topic :: Utilities
Project-URL: Homepage, https://github.com/marcelo-6/ftbatch-bulk-edit
Project-URL: Documentation, https://github.com/marcelo-6/ftbatch-bulk-edit
Project-URL: Repository, https://github.com/marcelo-6/ftbatch-bulk-edit.git
Project-URL: Issues, https://github.com/marcelo-6/ftbatch-bulk-edit/issues
Project-URL: Changelog, https://github.com/marcelo-6/ftbatch-bulk-edit/blob/main/CHANGELOG.md
Requires-Python: <3.15,>=3.14
Requires-Dist: lxml>=6.0.2
Requires-Dist: openpyxl>=3.1.5
Requires-Dist: rich>=12.3.0
Requires-Dist: typer>=0.24.0
Description-Content-Type: text/markdown

<!-- markdownlint-disable MD033 -->
# Bulk Editor for FactoryTalk Batch Recipes

[![CI](https://github.com/marcelo-6/ftbatch-bulk-edit/actions/workflows/ci.yml/badge.svg)](https://github.com/marcelo-6/ftbatch-bulk-edit/actions/workflows/ci.yml)
[![Publish](https://github.com/marcelo-6/ftbatch-bulk-edit/actions/workflows/publish-pypi.yml/badge.svg)](https://github.com/marcelo-6/ftbatch-bulk-edit/actions/workflows/publish-pypi.yml)
[![Coverage](https://img.shields.io/badge/coverage-gate%20%E2%89%A570%25-blue)](https://github.com/marcelo-6/ftbatch-bulk-edit/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/ftbatch-bulk-edit.svg)](https://pypi.org/project/ftbatch-bulk-edit/)

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.

<blockquote>
<p>[!NOTE]
Useful for large recipes with many defered parameters</p>
</blockquote>

---
<details>
<summary>Table of Contents</summary>

- [Bulk Editor for FactoryTalk Batch Recipes](#bulk-editor-for-factorytalk-batch-recipes)
  - [Features](#features)
  - [Requirements](#requirements)
  - [Installation](#installation)
    - [Using pip](#using-pip)
    - [Using uv as the Package Manager](#using-uv-as-the-package-manager)
    - [Using the Justfile (Recommended)](#using-the-justfile-recommended)
  - [Usage Overview](#usage-overview)
    - [Export XML to Excel (`xml2excel`)](#export-xml-to-excel-xml2excel)
    - [Import Excel to XML (`excel2xml`)](#import-excel-to-xml-excel2xml)
    - [Global Options](#global-options)
  - [Debug Mode](#debug-mode)
  - [Flow Chart](#flow-chart)
  - [Example Excel Output](#example-excel-output)
  - [Example XML Schema Snippet](#example-xml-schema-snippet)
  - [Preservation Rules \& Edge Cases](#preservation-rules--edge-cases)
  - [Troubleshooting](#troubleshooting)

</details>

## Features

1. **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.  

2. **Preserve Order & Empties**  
   - Maintains the original element order.  
   - Keeps empty tags (e.g. `<EngineeringUnits/>`, `<String/>`).  
   - Retains existing `<FormulaValueLimit>` blocks unless you remove them.  

3. **Recursive Child Parsing**  
   - A `.pxml` may reference `.uxml`; a `.uxml` may reference `.oxml` via `<StepRecipeID>`.  
   - Exports all related files into one workbook.

4. **Selective Create/Update/Delete**  
   - **Update** if a row’s `FullPath` matches an existing node.  
   - **Delete** if you remove a row entirely.  
   - **Create** if you add a new `FullPath` row (with one valid data-type column).

5. **Detailed Logging**  
   - Console keeps high-signal warnings/summaries with Rich formatting.  
   - DEBUG-level detail (with `--debug`) is written to `batch_bulk_editor.log`.

6. **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.

![cli](docs/images/cli.png)

![cli-results](docs/images/cli-example.png)

---

## Requirements

- Python 3.14 (in theory 3.10+)
- Dependencies
  - For xml and excel work:
    - lxml
    - openpyxl
  - For CLI:
    - typer

---
<details>
<summary>Installation</summary>

## Installation

### Using pip

1. **Clone** this repo.  
2. **Install** dependencies:

   ```bash
   pip ftbatch-bulk-edit
   ```

3. **Run**:

   ```bash
   ftbatch-bulk-edit xml2excel --xml yourRecipe.pxml --excel out.xlsx
   ```

### Using uv as the Package Manager

```bash
uv sync --python 3.14
uv run python app/main.py xml2excel --xml yourRecipe.pxml --excel out.xlsx
```

#### Using the example in the repo

```bash
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:

```bash
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
```

</details>

## Usage Overview

### Export XML to Excel (`xml2excel`)

```bash
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`)

```bash
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:

```bash
python app/main.py --debug xml2excel --xml myRecipe.pxml --excel out.xlsx
```

- Creates/appends `batch_bulk_editor.log` at 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:

```bash
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

```mermaid
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

```xml
<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>`’s `Defer` must match a parameter name in the same sheet.
  - Example output:

```bash
[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

1. **No Sheet Found**

   - Sheet name must match the XML filename (`TEST.pxml` → sheet “TEST.pxml”).
2. **Missing Data-Type**

   - Every Parameter or FormulaValue row must have one and only one data-type column.
3. **Need More Logs?**

   - Add `--debug` to capture detailed steps in `batch_bulk_editor.log`.
