Tools

Reference parsers in 4 languages, converters from formats you already have, and readers for RAG pipelines and AI agents.

A specification nobody can read is just a document. These are the reference implementations: they turn AODM into data your program already knows how to handle, and they check every rule in the specification while doing it.

All of it is Apache 2.0 licensed and ships in the download package. The parsers and converters have no dependencies in any language — each uses only its standard library. The RAG readers depend only on the framework they plug into.

Reference parsers

A parser reads AODM’s XML form and hands back the JSON model defined by aodm-core-1.3.schema.json. Both are AODM: the specification defines two serialisations of one model, and the parsers convert between them in either direction.

LanguageFileRequires
Pythonparsers/python/aodm.pyPython 3.8+, standard library
JavaScriptparsers/javascript/aodm.jsNode 18+ or any browser
Javaparsers/java/Aodm.javaJDK 11+, standard library
C#parsers/csharp/Aodm.cs.NET 6+, base class library

Python

from aodm import parse_file

doc = parse_file("knowledge.xml")
doc.facts                 # list of facts
doc.by_id("engine")       # look up any element
doc.to_json()             # JSON matching the published schema
doc.to_xml()              # back to AODM XML
doc.is_valid()            # False if any MUST-level rule is broken

for issue in doc.validate():
    print(issue)          # ERROR R1: The object "ghost" does not match…

JavaScript

const { parse } = require('./aodm');

const doc = parse(xmlString);
doc.facts;        doc.byId('engine');
doc.toJSON();     doc.toXml();
doc.isValid();    doc.validate();

In a browser, load it with a <script> tag and use AODMParser.parse(…). It uses the browser’s own XML parser where available and falls back to a built-in reader under Node, so there is no dependency either way.

Java

Aodm.Document doc = Aodm.parseFile("knowledge.xml");
doc.facts();      doc.byId("engine");
doc.toJson();     doc.toXml();
doc.isValid();    doc.validate();

C#

var doc = Aodm.ParseFile("knowledge.xml");
doc.Facts;        doc.ById("engine");
doc.ToJson();     doc.ToXml();
doc.IsValid();    doc.Validate();

Each also runs from the command line:

python3 aodm.py knowledge.xml              # print the JSON model
python3 aodm.py --validate knowledge.xml   # report rule violations

What the parsers check

Validation is not an optional extra bolted on afterwards — every parser enforces the full rule set from VALIDATION-RULES.md, including the constraints that no schema language can express: referential integrity, derivation cycles, duplicate ids, cardinality, measurement coherence, temporal ordering, polarity handling and digest format. Errors are MUST-level violations; warnings are SHOULD-level.

Why four implementations rather than one. Beyond covering more stacks, independent implementations are how a specification gets tested. If two of them disagreed about the same document, the fault would be in the specification, not the code. A conformance suite runs all four over the same inputs and requires identical output — currently 18 rule cases across four languages, alongside 21 schema checks, 24 XBRL constructs and 33 graph and reasoning checks.

Converters

Most organisations already hold structured data in a standard format. A converter turns that into AODM without re-authoring anything, which is usually the difference between adopting a format and reading about it.

XBRL

XBRL is mandatory for financial reporting to the SEC, ESMA, HMRC and most other regulators, so essentially every listed company already produces it. The converter implements the whole of the XBRL 2.1 instance document syntax.

python3 xbrl_to_aodm.py instance.xml             # AODM JSON
python3 xbrl_to_aodm.py instance.xml --xml      # AODM XML
python3 xbrl_to_aodm.py instance.xml --validate
python3 xbrl_to_aodm.py instance.xml --labels=filer_lab.xml --uri=https://www.sec.gov/...

Pass the filing’s label file with --labels and every fact is named the way the filer names it, with its segment and period, for example “Net sales [Operating segments; Greater China] (Apple Inc.; period)”, instead of a raw element name. --uri records the filing’s address as each fact’s source.

XBRLBecomes
Reporting entity identifierentity type="reporting-entity"
Reported factfact, statement = element name
Numeric content + unitvalue/@number + @unit
Divided unit (USD/share)@unit="USD/shares"
Fraction@number, numerator ÷ denominator
Duration periodvalid-from / valid-to
Instant periodvalid-from = valid-to
Forever periodno temporal bounds
@decimals / @precisionvalue/@tolerance
Explicit & typed dimensionsrelationship to a member entity
Tuples, nestedentity type="fact-group" + composition
Footnotesappended to the fact statement

A stated accuracy of decimals="-6" means the figure is rounded to millions, so the real value sits within half a million either way. That becomes an AODM tolerance rather than being discarded — which is the whole point: the precision of a reported number is information, and most pipelines throw it away.

Scope, stated precisely. This covers the XBRL 2.1 instance document syntax completely. It does not interpret the taxonomy — linkbases, label resolution and calculation trees live in separate files an instance only references. The one exception is the label linkbase, which --labels reads so facts carry the filer’s own names; without it, element names appear verbatim. Inline XBRL (iXBRL embedded in HTML) is a different serialisation and is not read.

Spreadsheets and CMS exports

Turns a product list, price sheet or CMS export (CSV, TSV or XLSX) into AODM. No libraries are needed, Excel included.

python3 converters/sheet_to_aodm.py products.xlsx --validate
  • The first column names the thing each row describes. Every other filled cell becomes a fact about it.
  • A unit in the header, such as Weight (kg) or Price (EUR), becomes the fact’s unit.
  • Columns headed URL or Link, Published and Modified become each fact’s source and date. Valid from and Valid to columns become its validity window.
  • An Excel formula becomes a derived fact that points at the cells it was calculated from.
  • An AI-written column marks rows a model wrote. Those rows need a value in a Confidence column, or the converter stops and names the cells.

Schema.org JSON-LD

Reads the Schema.org JSON-LD already in a page, or a .jsonld file, and writes it as AODM: products, offers, articles, events, recipes and product variants.

python3 converters/jsonld_to_aodm.py page.html --validate

Prices keep their currency and validity dates, ratings become measured values, and the page’s publisher and dates become the source. Output is a draft to review, because JSON-LD often says less than the page does.

Product feeds

Converts a Google Merchant Center product feed (RSS or Atom XML, CSV or TSV): prices, sale prices with their dates, availability, sizes, weights, shipping costs, brands and product variants.

python3 converters/feed_to_aodm.py feed.xml --currency=USD --validate
python3 converters/feed_to_aodm.py feed.tsv --uri=https://shop.example/feed.tsv --modified=2026-10-01

--currency sets the currency for prices written without one, --uri records the feed’s address as the source, and --modified sets the date the feed was current. Unit prices are worked out from price and size and marked as derived.

Page-level defaults

AODM 1.3 lets a page declare once what all its facts share, such as the source, the date or the unit, in a data-aodm-defaults attribute. This tool moves shared values out of each tag and into that attribute, or expands them back for tools that read HTML without a browser.

python3 converters/aodm_defaults.py compact page.html > smaller.html
python3 converters/aodm_defaults.py expand page.html > expanded.html

The rules readers follow are in section 3.9 of the HTML profile. The online validator and the Python readers below expand defaults automatically.

Graph compiler

Compiles AODM into graph structures for reasoning engines and graph databases. Engine ──requires──▶ Spark becomes node, edge, node.

python3 aodm_graph.py doc.xml --format cypher    # Neo4j
python3 aodm_graph.py doc.xml --format turtle    # RDF, for SPARQL/OWL
python3 aodm_graph.py doc.xml --format graphml   # Gephi, yEd
python3 aodm_graph.py doc.xml --format dot       # Graphviz

Facts, rules and sources become nodes rather than properties. That is not a stylistic choice: with sources as nodes, “what rests on this source?” is one traversal, and retraction becomes possible. Flattened into text properties it is a full scan with string matching.

Inference engine

Forward chaining over the compiled graph, implementing the inference section of the specification (section 10), so its conclusions are reproducible by any other conformant engine.

$ python3 reasoner.py doc.xml
Derived 2 conclusion(s):
  step 1: failure-risk     confidence 0.5985
           by risk-rule from temp-rise, pressure-nominal
  step 1: shorten-service  confidence 0.53865
           by service-rule from failure-risk

Confidence multiplies along the chain, so a conclusion is never more certain than the weakest premise supporting it. It also answers two questions a reasoner usually cannot:

  • Why is this believed? --explain walks the chain back to the observed facts and their sources.
  • What falls if this source is wrong? --retract follows the derivations forward and lists every conclusion that depended on it.
  • What was true then? --at 2025-06-01 reasons as at a date; a rule outside its validity window does not fire.

LLM context

Renders a document into prompt-ready context for any model. Dropping raw facts into a prompt is easy and mostly useless: the model gets a pile of assertions with no way to tell a measurement from a guess, and states them all with equal confidence.

python3 context.py doc.xml                   # markdown
python3 context.py doc.xml --format text     # compact, for tight budgets
python3 context.py doc.xml --format json     # structured, for tool use
python3 context.py doc.xml --max-chars 4000  # budget-aware

The renderer states confidence in words as well as numbers, marks known-false facts explicitly rather than omitting them, excludes superseded knowledge while saying that it did, and labels inferred facts as inferred. Those are the signals that let a model hedge accurately instead of asserting everything flatly.

Use AODM in RAG pipelines and AI agents

Most retrieval pipelines turn HTML into plain text first, and that step throws the data-aodm attributes away. These readers parse the page before any text conversion, split it into search passages that never cut a tagged element in half, and keep each passage’s facts, sources and dates attached.

They ship in the package as source. Install them with pip from the unpacked package’s top folder:

pip install ./integrations/aodm-reader                                       # the reader on its own
pip install ./integrations/aodm-reader ./integrations/langchain-aodm         # LangChain
pip install ./integrations/aodm-reader ./integrations/llama-index-readers-aodm   # LlamaIndex
pip install ./integrations/aodm-reader ./integrations/aodm-mcp               # MCP server

The reader

from aodm_reader import load_passages, format_passage

docs = load_passages(["site/"])         # passages with their AODM records
print(format_passage(docs[0]))          # the passage plus 1 line per fact

By default it leaves out text a page marks as AI-written, and it can hide facts that are outside their validity window on a given date or have been superseded.

LangChain

from langchain_aodm import AODMLoader, AODMRetriever, format_document

docs = AODMLoader(["site/"]).load()
retriever = AODMRetriever(base_retriever=store.as_retriever(), as_of="2026-10-06", k=5)

LlamaIndex

from llama_index.readers.aodm import AODMReader

documents = AODMReader().load_data(["site/"])

MCP server

Lets an AI model such as Claude search your tagged pages directly, with filters for date, AI-written text and source. It is read-only.

claude mcp add aodm -- aodm-mcp /path/to/your/site

For Claude Desktop and other clients, the configuration is in integrations/aodm-mcp/README.md in the package.