Represent HTML and XML using Python data structures.
Project description
Mu-XML
Represent XML using Python data structures. This does for Python what the Hiccup library by James Reeves did for the Clojure language.
Warning: this library is still alpha. So expect breaking changes.
Install
pip install mu-xml
# or
uv add mu-xml
Usage
To render a Mu data structure as XML markup use the xml function.
from mu import xml
xml(["p", "Hello, ", ["b", "World"], "!"])
Returns the string <p>Hello, <b>World</b>!</p>
Note that serializing to a string will not guarantee well-formed XML.
Documentation
XML is a tree data structure made up of various node types such as element, attribute, or text nodes.
However, writing markup in code is tedious and error-prone. Mu allows creating markup with Python code and basic Python data structures.
Element nodes
An element node is made up of a tag, an optional attribute dictionary and zero or more content nodes which themselves can be made up of other elements.
el = ["p", {"id": 1}, "this is a paragraph."]
You can access the individual parts of an element node using the following accessor functions.
import mu
mu.tag(el) # "p"
mu.attrs(el) # {"id": 1}
mu.content(el) # ["this is a paragraph."]
mu.get_attr("id", el) # 1
To render this as XML markup:
from mu import xml
xml(el) # <p id="1">this is a paragraph.</p>
Use the provided predicate functions to inspect a node.
import mu
mu.is_element(el) # is this a valid element node?
mu.is_special_node(el) # is this a special node? (see below)
mu.is_empty(el) # does it have child nodes?
mu.has_attrs(el) # does it have attributes?
Special nodes
XML has a few syntactic constructs that you usually don't need. But if you do need them, you can represent them in Mu as follows.
["$comment", "this is a comment"]
["$pi", "foo", "bar"]
["$cdata", "<foo>"]
["$raw", "<foo/>"]
["$text" "<foo>"]
These will be rendered as:
<!-- this is a comment -->
<?foo bar?>
<![CDATA[<foo>]]>
<foo/>
<foo>
Nodes with tag names that start with $ are reserved for other applications. The xml() function will drop special nodes that it does not recognize.
A $cdata node will not escape it's content as is usual in XML and HTML. A $raw node is very useful for adding string content that already contains markup.
A $comment node will ensure that the forbidden -- is not part of the comment text.
Namespaces
Mu does not enforce XML rules. You can use namespaces but you have to provide the namespace declarations as is expected by XML Namespaces.
["svg", dict(xmlns="http://www.w3.org/2000/svg"),
["rect", dict(width=200, height=100, x=10, y=10)]
]
<svg xmlns="http://www.w3.org/2000/svg">
<rect height="100" width="200" x="10" y="10"/>
</svg>
The following uses explicit namespace prefixes and is semantically identical to the previous example.
["svg:svg", {"xmlns:svg": "http://www.w3.org/2000/svg"},
["svg:rect", {"width": 200, "height": 100, "x": 10, "y": 10}]
]
<svg:svg xmlns:svg="http://www.w3.org/2000/svg">
<svg:rect widht="200" height="100" x="10" y="10"/>
</svg:svg>
Object nodes
Object nodes may appear in two positions inside a Mu data structure.
- In the content position of an element node (e.g.
["p", {"class": "x"}, obj]) or, - In the tag position of an element node (e.g.
[obj, {"class": "x"}, "content"])
Object nodes can be derived from the mu.Node class. See the example below.
from mu import Node, xml
class UL(Node):
def __init__(self, **attrs):
super().__init__("ul", **attrs)
def __call__(self, *nodes, **attrs):
nodes = [["li", node] for node in nodes]
return super().__call__(*nodes, **attrs)
Let's use this class in a Mu data structure.
xml(["div", UL(), "foo"])
<div><ul/>foo</div>
Here the UL() object is in the content position so no information is passed to it to render a list. This may not be what you wanted to achieve.
To produce a list the object must be in the tag position of an element node.
xml(["div", [UL(), {"class": ("foo", "bar")}, "item 1", "item 2", "item 3"]])
<div>
<ul class="foo bar">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
</div>
You can also provide some initial content and attributes in the object node constructor.
xml(["div", [UL(id=1, cls=("foo", "bar")), "item 1", "item 2", "item 3"]])
Note that we cannot use the reserved class keyword, instead use cls to get a class attribute.
<div>
<ol class="foo bar" id="1">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ol>
</div>
Expand nodes
In some cases you may want to use the mu.expand function to only expand object nodes to a straightforward data structure.
from mu import expand
expand(["div", [OL(), {"class": ("foo", "bar")}, "item 1", "item 2", "item 3"]])
["div",
["ol", {"class": ("foo", "bar")},
["li", "item 1"],
["li", "item 2"],
["li", "item 3"]]]
Serializing Python data structures
mu.dumps(["a",True,3.0])
mu.loads(['_', {'as': 'array'},
['_', 'a'],
['_', {'as': 'boolean', 'value': 'true()'}],
['_', {'as': 'float'}, 3.0]])
mu.dumps(dict(a="a",b=True,c=3.0))
mu.loads(['_', {'as': 'object'},
['a', 'a'],
['b', {'as': 'boolean', 'value': 'true()'}],
['c', {'as': 'float'}, 3.0]])
When dumps() encounters a Python object it will call it's mu() method if it exists otherwise it will not be part of the serialized result. A function object will be called and it's return value becomes part of the serialized result.
Develop
- Install uv.
uv tool add ruff- Maybe install
RuffVS Code extension
Run linter.
uvx ruff check
Run formatter.
uvx ruff format
Run tests.
uv run pytest
Or with coverage and missing lines.
uv run pytest --cov-report term-missing --cov=mu
Related work
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
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 mu_xml-0.1.3.tar.gz.
File metadata
- Download URL: mu_xml-0.1.3.tar.gz
- Upload date:
- Size: 29.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cecb0f81175a962fe48b75e7cf92b147a4381aed90af6516365a4972754f8af8
|
|
| MD5 |
7f438d6ce579ac6a1a37d4c33da45a41
|
|
| BLAKE2b-256 |
98a188cc3261420a1488ce93ea1449cdf3b271b28b551e6f96c35338a965fcc1
|
File details
Details for the file mu_xml-0.1.3-py3-none-any.whl.
File metadata
- Download URL: mu_xml-0.1.3-py3-none-any.whl
- Upload date:
- Size: 9.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92639696758ddd0eee9576004ad386aed58df374d9a6911e1670e9e55fa99751
|
|
| MD5 |
2c63e28125e5c90c8b533f13d3464aac
|
|
| BLAKE2b-256 |
21306fcdfdcbcbbffdb5fe336ef79f6b4b303d1f95c6f5aa7a1879bc8569aa5a
|