Practical, ready-to-use tools for LLM generated XML
Project description
fix-llm-xml
Practical, ready-to-use tools for LLM generated XML
Your precious tokens will not be eaten by XML parsers!
fix-llm-xml is specifically designed to solve common formatting errors that occur when Large Language Models (LLMs) output XML.
It gracefully handles unescaped special characters, mismatched tags, malformed CDATA, missing closing tags and more, while guaranteeing full integrity of content inside specified plaintext tags.
Validated by over 100 random tests and numerous real-world scenarios, it easily handles all kinds of corrupted XML produced by LLMs.
Key Features
- 🧩 Smart Parsing –
parse_xmlprovides one-click repair + parsing, automatically repair if the first attempt fails. - 📃 Text Content Preservation – Automatically escapes and retains original content for user-specified plaintext tags (and CDATA content), preventing data loss during automatic repair.
- 🔧 Automatic Tag Structure Repair – Completes missing closing tags, discards redundant closing tags.
- 📦 Deep CDATA Repair – Handles nested CDATA, malformed CDATA prefixes, incorrect CDATA closing formats, and automatically escapes internal
]]>to a safe form. - 🌍 Namespace Support – Supports tags with namespace prefixes (e.g.
ns:code). - 🧪 Comprehensively Tested – Combines traditional unit testing with Hypothesis property-based random testing to verify correctness under all kinds of extreme conditions.
- 📁 Minimal Dependencies – Only depends on
lxmlandxmltodict, suitable for production environments.
Installation
pip install fix-llm-xml
Quick Start
Suppose your LLM returns the following "unclean" XML:
Some irrelevant prefix text
<result>
<answer>if a < b && c > d</answer>
<file name="a.html"><![CDATA[<!DOCTYPE html><html lang="en">...</html>]></file>
<meta>
<confidence>0.95</confidence>
</result>
Use fix_llm_xml.parse_xml to parse it in one click, outputting a dictionary in xmltodict format:
from fix_llm_xml import parse_xml
llm_output = '''
Some irrelevant prefix text
<result>
<answer>if a < b && c > d</answer>
<file name="a.html"><![CDATA[<!DOCTYPE html><html lang="en">...</html>]></file>
<meta>
<confidence>0.95</confidence>
</result>
'''
parsed = parse_xml(llm_output, root="result")
print(parsed)
# {'result': {'answer': 'if a < b && c > d', 'file': {'@name': 'a.html', '#text': '<!DOCTYPE html><html lang="en">...</html>'}}}
API Reference
parse_xml(s, root, text_tags=None, **kwargs)
All-in-one function to extract, repair, and parse XML from messy strings into Python dictionaries (xmltodict format).
Internally it first calls find_xml_document and parses with xmltodict. If there are nested tags in text_tags or direct parsing fails, it calls fix_xml_with_text_tags + lxml repair for a second parsing attempt.
Parameters:
s(str): Original input string.root(str): Expected root tag name.text_tags(Optional[List[str]]): List of known plaintext tags, passed tofix_xml_with_text_tags.**kwargs: Additional parameters passed toxmltodict.parse, common options include:force_list(List[str]): Force specified tags to always be parsed as lists (even if only one instance exists).strip_whitespace(bool): DefaultTrue, whether to strip leading/trailing whitespace from text values.
Return Value:
Optional[Dict]: Parsed dictionary; returnsNoneif all repair attempts fail.
Example:
text = """
aaaaa
<response>
<data>
<item id="1">Apple</item>
<item id="2">Orange</item>
</data>
<status>Success
</response>
bbbbb
"""
result = fix_llm_xml.parse_xml(text, "response", force_list=["item"])
print(result)
# {
# 'response': {
# 'data': {
# 'item': [
# {'@id': '1', '#text': 'Apple'},
# {'@id': '2', '#text': 'Orange'}
# ]
# },
# 'status': 'Success'
# }
# }
find_xml_document(s, root, with_tag=False, cdata=False)
Extracts content inside <root> tags from a large string that may be mixed with non-XML text.
Algorithm: Finds the opening tag from left to right, finds the closing tag from right to left.
Parameters:
s(str): Input string.root(str): Name of the target root tag.with_tag(bool): IfTrue, returned result includes the opening and closing tags themselves; ifFalse, only returns content inside the tags.cdata(bool): IfTrue, expects the root tag to be immediately followed by<![CDATA[...]]>wrapping, returns plain text inside CDATA.
Return Value:
Optional[str]: Extracted content string; returnsNoneif target tag cannot be found.
Example:
s = "Prefix text<root>Core content</root>Suffix text"
find_xml_document(s, "root") # Returns "Core content"
find_xml_document(s, "root", with_tag=True) # Returns "<root>Core content</root>"
get_xml_tag_text(value)
Safely extracts text content inside tags from xmltodict parsing results. Handles all possible return value formats (strings, dictionaries, lists, etc.).
Parameters:
value: Value corresponding to a tag from theparse_xmlresult dictionary.
Return Value:
str: Extracted text;- If value is
None→ returns''; - If it is a string → returns directly;
- If it is a dictionary with
#textkey → returns value of#text; - If it is a list → takes first element and processes recursively;
- Other cases → returns
''.
- If value is
Example:
parsed = {'tag': [{'#text': 'hello', '@attr': 'x'}]}
get_xml_tag_text(parsed['tag']) # Returns 'hello'
fix_xml_with_text_tags(xml, text_tags=())
Repairs corrupted XML strings to make them structurally valid and processable by lenient XML parsers (such as lxml). For example, if downstream users do not want to use xmltodict, they can directly call this function for repair.
Parameters:
xml(str): XML string to be repaired.text_tags(Iterable[str]): Iterable of plaintext tag names (case-sensitive, supportsns:tagformat). These tags are considered to contain only plain text, no internal child elements will be parsed; characters such as<,>,&inside will be properly escaped, or protected via CDATA.
Return Value:
str: Valid XML string after repair.
Core Behaviors:
- Automatically closes unclosed regular tags, deletes redundant closing tags.
- Automatically escapes text, will not re-escape already escaped content.
- For plaintext tags (listed in text_tags, or wrapped with CDATA):
- Automatically detects CDATA usage (including malformed CDATA prefixes/suffixes) and standardizes it.
- In CDATA mode, escapes nested
]]>to]]]]><![CDATA[>; will not re-escape already escaped content. - In normal mode, escapes
<,>,&to corresponding XML entities; will not re-escape already escaped entities.
- Retains all attributes, comments, self-closing tags.
- Does not guarantee 100% compliance with XML specifications, does not support advanced XML features; output may still have logical issues, requires re-parsing by a lenient XML parser.
Example:
malformed = "<root><code>if a < b && c > d</code></root>"
fixed = fix_xml_with_text_tags(malformed, ["code"])
print(fixed)
# <root><code>if a < b && c > d</code></root>
Usage Scenarios and Basic Assumptions
This library assumes your workflow is as follows:
- You send a prompt to an LLM, requiring it to return structured data in XML format.
- The text returned by the LLM may contain other natural language text before and after the XML.
- You specify in advance which tags are "plaintext tags" (e.g.
<file>,<answer>), or instruct the LLM to wrap text content in CDATA. The content of these tags should not be parsed as XML child elements, but as complete plaintext/code snippets. - You want to reasonably parse this output without losing any text content.
This library provides a complete set of tools for the above process, especially good at handling the following common LLM output errors:
- Unescaped special characters:
1 < 2appears in regular text tags. - Incorrectly written CDATA:
<CDATA[,[CDATA[,]>etc. - Nested XML content is incorrectly wrapped into the same tag.
- Missing closing tags, or extra redundant closing tags.
Testing and Quality Assurance
fix-llm-xml has passed a large number of multi-level tests to ensure correct operation under various extreme and random conditions.
- Conventional Unit Tests: Covers basic functions, edge cases, examples in documentation, and nearly a hundred deliberately constructed malformed XML samples.
- Property-based Random Testing: Uses Hypothesis to generate a large number of random, unbalanced XML fragments to verify the following invariants:
- All tags in the repaired XML are properly closed.
- The result remains unchanged after re-repairing already repaired XML.
- Original content inside plaintext tags can be fully recovered after repair.
- The escape function will not introduce unescaped special characters, nor will it perform secondary escaping.
- Complex Nested Scenarios: Specifically tested for easily confusing cases such as multi-level nested CDATA, nested tags with the same name, CDATA containing character sequences identical to closing tags, etc.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fix_llm_xml-1.1-py3-none-any.whl.
File metadata
- Download URL: fix_llm_xml-1.1-py3-none-any.whl
- Upload date:
- Size: 12.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd972b1e7076bd8a9a4f581dfe5529765729634d66776b4051e249d8e556075a
|
|
| MD5 |
46f330dba77206b3428ee80c14020bb1
|
|
| BLAKE2b-256 |
578e68351a173f50da9f4eb6d63d0abe0593ff3b17cffb3c6abd61b7ef0ae301
|