This project converts an Ampla Project XML export into an ISA‑95 B2MML Equipment model.
It replaces the original XSLT‑only approach with a modern Python implementation featuring:
- a command‑line interface
- a FastAPI service
- multiple output formats
- a fully tested, deterministic multi‑pass transformation pipeline
Ampla Project XML files contain:
- equipment hierarchy
- equipment classes
- class inheritance
- class and instance properties
This tool parses the XML and produces:
- B2MML Equipment XML
- JSON model
- Excel workbook (.xlsx) with Equipment and Classes sheets
- CSV exports
- HTML report
Additional capabilities:
- Validation warnings for structural issues (unknown class IDs, malformed items, missing config)
- Diff mode to compare two Ampla configurations
- Statistics summary (equipment counts, class counts, property totals, hierarchy depth)
The transformer is deterministic, regression‑tested, and suitable for automation, CI, and configuration comparison.
The transformation pipeline is implemented in a multi‑pass class:
from app.transformers.ampla import AmplaTransformerThe pipeline performs:
- Class ID lookup construction
- Class hierarchy extraction
- Inheritance chain computation
- Equipment tree parsing
- Full‑name resolution
- Property merging (class inheritance + instance overrides)
- Sorting and normalization
This reproduces the behaviour of the original XSLT while adding validation, configurability, and structured warnings.
The transformer produces a clean, normalized internal representation of the Ampla project.
This model is independent of any output format (XML, JSON, Excel) and is used by all builders.
classDiagram
class Equipment {
+String id
+String name
+String level
+List class_ids
+List children
+Dict overrides
+List properties
+String full_name
}
class EquipmentClass {
+String name
+String parent
+List properties
+List inheritance_chain
}
class ClassProperty {
+String name
+String value
+String datatype
}
class EquipmentProperty {
+String name
+String value
+String datatype
}
Equipment "1" *-- "many" Equipment : children
Equipment "1" *-- "many" EquipmentProperty : properties
EquipmentClass "1" *-- "many" ClassProperty : properties
Equipment ..> EquipmentClass : resolves attributes from
sequenceDiagram
participant XML as Raw XML
participant AT as AmplaTransformer
participant CTX as Context (Shared State)
participant Model as Internal Model
AT->>XML: Pass 1: Build ID Lookup Table
AT->>XML: Pass 2: Extract Classes & Compute Inheritance
AT->>XML: Pass 3: Recursive Equipment Tree Parsing
AT->>CTX: Resolve Class IDs & Log Warnings
AT->>Model: Pass 4: Merge Properties & Apply Overrides
Note right of Model: Final deterministic B2MML-ready model
- Deterministic output — same XML → same model every time
- Multi-pass resolution — ensures class inheritance and overrides are fully resolved
- Separation of concerns — parsing, transformation, and output builders are isolated
- Extensible — new output formats can be added without touching the transformer
- Safe — warnings collected instead of throwing on malformed XML
---
## **Configuration (mapping.toml)**
Level mapping is now externalized in:
config/mapping.toml
Example:
```toml
[level_map]
"Citect.Ampla.Isa95.EnterpriseFolder" = "Enterprise"
"Citect.Ampla.Isa95.SiteFolder" = "Site"
"Citect.Ampla.Isa95.AreaFolder" = "Area"
"Citect.Ampla.General.Server.ApplicationsFolder" = "Other"
If the file is missing or invalid, the transformer logs a warning and falls back to defaults.
from lxml import etree
from app.transformers.ampla import AmplaTransformer
# Parse XML
root = etree.parse("input.xml").getroot()
# Create transformer (loads mapping.toml automatically)
transformer = AmplaTransformer(config_path="config/mapping.toml")
# Run transformation
model = transformer.transform(root)
print(model["equipment"])
print(model["classes"])
print(model["warnings"]) # non-fatal issues detected during parsingThe returned model is a dictionary:
{
"equipment": [...],
"classes": [...],
"warnings": [...]
}b2mml convert input.xml output.xml
b2mml json input.xml output.json
Write to stdout:
b2mml json input.xml
b2mml excel input.xml output.xlsx
b2mml stats input.xml
As JSON:
b2mml stats --format json input.xml
b2mml html input.xml report.html
b2mml diff baseline.xml updated.xml
As JSON:
b2mml diff --format json baseline.xml updated.xml
Exit codes:
0→ no differences1→ differences found
Start the FastAPI server:
uvicorn app.api:app --reload
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /info |
API and pipeline version info |
| POST | /convert/json |
JSON model |
| POST | /convert/xml |
B2MML XML |
| POST | /convert/excel |
Excel workbook |
| POST | /convert/csv/equipment |
Equipment CSV |
| POST | /convert/csv/classes |
Classes CSV |
| POST | /convert/html |
HTML report |
| POST | /stats |
Model statistics |
| POST | /diff/json |
JSON diff |
| POST | /diff/text |
Text diff |
Examples:
curl -X POST -F "file=@input.xml" http://localhost:8000/convert/json
curl -X POST -F "file=@input.xml" http://localhost:8000/stats
curl -X POST -F "file_a=@baseline.xml" -F "file_b=@updated.xml" http://localhost:8000/diff/text
Interactive docs:
http://localhost:8000/docs
Start the API:
make up
Service available at:
http://localhost:8000
Stop:
make down
app/parsers— XML parsingapp/transformers— Ampla → internal modelapp/builders— B2MML XML serializationapp/validators.py— model validation + warningsapp/diff.py— structural diff engineapp/stats.py— statistics computationapp/excel_export.py— Excel exportapp/csv_export.py— CSV exportapp/html_report.py— HTML report generatorapp/cli.py— CLI entrypointsapp/api.py— FastAPI serviceapp/schemas.py— Pydantic response modelstests/— full test suite + regression fixtures
BSD 3‑Clause License.
Includes derivative work from the original
Ampla_to_Equipment_B2MML.xslt by Oleg Tkachenko (2005),
also under BSD 3‑Clause.