Name | mu-xml JSON |
Version |
0.1.3
JSON |
| download |
home_page | None |
Summary | Represent HTML and XML using Python data structures. |
upload_time | 2025-08-18 18:17:36 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.12 |
license | None |
keywords |
html
templates
xml
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Mu-XML
Represent XML using Python data structures. This does for Python what the [Hiccup](https://github.com/weavejester/hiccup) library by James Reeves did for the Clojure language.
Warning: this library is still alpha. So expect breaking changes.
## Install
```shell
pip install mu-xml
# or
uv add mu-xml
```
## Usage
To render a Mu data structure as XML markup use the `xml` function.
```python
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.
```python
el = ["p", {"id": 1}, "this is a paragraph."]
```
You can access the individual parts of an element node using the following accessor functions.
```python
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:
```python
from mu import xml
xml(el) # <p id="1">this is a paragraph.</p>
```
Use the provided predicate functions to inspect a node.
```python
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.
```python
["$comment", "this is a comment"]
["$pi", "foo", "bar"]
["$cdata", "<foo>"]
["$raw", "<foo/>"]
["$text" "<foo>"]
```
These will be rendered as:
```xml
<!-- 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](https://www.w3.org/TR/xml-names).
```python
["svg", dict(xmlns="http://www.w3.org/2000/svg"),
["rect", dict(width=200, height=100, x=10, y=10)]
]
```
```xml
<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.
```python
["svg:svg", {"xmlns:svg": "http://www.w3.org/2000/svg"},
["svg:rect", {"width": 200, "height": 100, "x": 10, "y": 10}]
]
```
```xml
<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.
1) In the content position of an element node (e.g. `["p", {"class": "x"}, obj]`) or,
2) 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.
```python
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.
```python
xml(["div", UL(), "foo"])
```
```xml
<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.
```python
xml(["div", [UL(), {"class": ("foo", "bar")}, "item 1", "item 2", "item 3"]])
```
```xml
<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.
```python
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.
```xml
<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.
```python
from mu import expand
expand(["div", [OL(), {"class": ("foo", "bar")}, "item 1", "item 2", "item 3"]])
```
```python
["div",
["ol", {"class": ("foo", "bar")},
["li", "item 1"],
["li", "item 2"],
["li", "item 3"]]]
```
### Serializing Python data structures
```python
mu.dumps(["a",True,3.0])
```
```python
mu.loads(['_', {'as': 'array'},
['_', 'a'],
['_', {'as': 'boolean', 'value': 'true()'}],
['_', {'as': 'float'}, 3.0]])
```
```python
mu.dumps(dict(a="a",b=True,c=3.0))
```
```python
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](https://github.com/astral-sh/uv).
- `uv tool add ruff`
- Maybe install `Ruff` VS Code extension
Run linter.
```shell
uvx ruff check
```
Run formatter.
```shell
uvx ruff format
```
Run tests.
```shell
uv run pytest
```
Or with coverage and missing lines.
```shell
uv run pytest --cov-report term-missing --cov=mu
```
## Related work
- [weavejester/hiccup](https://github.com/weavejester/hiccup)
- [nbessi/pyhiccup](https://github.com/nbessi/pyhiccup)
- [SXML](https://en.wikipedia.org/wiki/SXML)
Raw data
{
"_id": null,
"home_page": null,
"name": "mu-xml",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "html, templates, xml",
"author": null,
"author_email": "xokomola <marc.van.grootel@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/98/a1/88cc3261420a1488ce93ea1449cdf3b271b28b551e6f96c35338a965fcc1/mu_xml-0.1.3.tar.gz",
"platform": null,
"description": "# Mu-XML\n\nRepresent XML using Python data structures. This does for Python what the [Hiccup](https://github.com/weavejester/hiccup) library by James Reeves did for the Clojure language.\n\nWarning: this library is still alpha. So expect breaking changes.\n\n\n## Install\n\n```shell\npip install mu-xml\n# or\nuv add mu-xml\n```\n\n\n## Usage\n\nTo render a Mu data structure as XML markup use the `xml` function.\n\n```python\nfrom mu import xml\n\nxml([\"p\", \"Hello, \", [\"b\", \"World\"], \"!\"])\n```\n\nReturns the string `<p>Hello, <b>World</b>!</p>`\n\nNote that serializing to a string will not guarantee well-formed XML.\n\n\n## Documentation\n\nXML is a tree data structure made up of various node types such as element, attribute, or text nodes.\n\nHowever, writing markup in code is tedious and error-prone. Mu allows creating markup with Python code and basic Python data structures.\n\n\n### Element nodes\n\nAn 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.\n\n```python\nel = [\"p\", {\"id\": 1}, \"this is a paragraph.\"]\n```\n\nYou can access the individual parts of an element node using the following accessor functions.\n\n```python\nimport mu\n\nmu.tag(el) # \"p\"\nmu.attrs(el) # {\"id\": 1}\nmu.content(el) # [\"this is a paragraph.\"]\nmu.get_attr(\"id\", el) # 1\n```\n\nTo render this as XML markup:\n\n```python\nfrom mu import xml\n\nxml(el) # <p id=\"1\">this is a paragraph.</p>\n```\n\nUse the provided predicate functions to inspect a node.\n\n```python\nimport mu\n\nmu.is_element(el) # is this a valid element node?\nmu.is_special_node(el) # is this a special node? (see below)\nmu.is_empty(el) # does it have child nodes?\nmu.has_attrs(el) # does it have attributes?\n```\n\n\n### Special nodes\n\nXML 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.\n\n```python\n[\"$comment\", \"this is a comment\"]\n[\"$pi\", \"foo\", \"bar\"]\n[\"$cdata\", \"<foo>\"]\n[\"$raw\", \"<foo/>\"]\n[\"$text\" \"<foo>\"]\n```\n\nThese will be rendered as:\n\n```xml\n<!-- this is a comment -->\n<?foo bar?>\n<![CDATA[<foo>]]>\n<foo/>\n<foo>\n```\n\nNodes with tag names that start with `$` are reserved for other applications. The `xml()` function will drop special nodes that it does not recognize.\n\nA `$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.\n\nA `$comment` node will ensure that the forbidden `--` is not part of the comment text.\n\n\n### Namespaces\n\nMu does not enforce XML rules. You can use namespaces but you have to provide the namespace declarations as is expected by [XML Namespaces](https://www.w3.org/TR/xml-names).\n\n```python\n[\"svg\", dict(xmlns=\"http://www.w3.org/2000/svg\"),\n [\"rect\", dict(width=200, height=100, x=10, y=10)]\n]\n```\n\n```xml\n<svg xmlns=\"http://www.w3.org/2000/svg\">\n <rect height=\"100\" width=\"200\" x=\"10\" y=\"10\"/>\n</svg>\n```\n\nThe following uses explicit namespace prefixes and is semantically identical to the previous example.\n\n```python\n[\"svg:svg\", {\"xmlns:svg\": \"http://www.w3.org/2000/svg\"},\n [\"svg:rect\", {\"width\": 200, \"height\": 100, \"x\": 10, \"y\": 10}]\n]\n```\n\n```xml\n<svg:svg xmlns:svg=\"http://www.w3.org/2000/svg\">\n <svg:rect widht=\"200\" height=\"100\" x=\"10\" y=\"10\"/>\n</svg:svg>\n```\n\n\n### Object nodes\n\nObject nodes may appear in two positions inside a Mu data structure.\n\n1) In the content position of an element node (e.g. `[\"p\", {\"class\": \"x\"}, obj]`) or,\n2) In the tag position of an element node (e.g. `[obj, {\"class\": \"x\"}, \"content\"]`)\n\nObject nodes can be derived from the `mu.Node` class. See the example below.\n\n```python\nfrom mu import Node, xml\n\nclass UL(Node):\n def __init__(self, **attrs):\n super().__init__(\"ul\", **attrs)\n\n def __call__(self, *nodes, **attrs):\n nodes = [[\"li\", node] for node in nodes]\n return super().__call__(*nodes, **attrs)\n```\n\nLet's use this class in a Mu data structure.\n\n```python\nxml([\"div\", UL(), \"foo\"])\n```\n\n```xml\n<div><ul/>foo</div>\n```\n\nHere 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.\n\nTo produce a list the object must be in the tag position of an element node.\n\n```python\nxml([\"div\", [UL(), {\"class\": (\"foo\", \"bar\")}, \"item 1\", \"item 2\", \"item 3\"]])\n```\n\n```xml\n<div>\n <ul class=\"foo bar\">\n <li>item 1</li>\n <li>item 2</li>\n <li>item 3</li>\n </ul>\n</div>\n```\n\nYou can also provide some initial content and attributes in the object node constructor.\n\n```python\nxml([\"div\", [UL(id=1, cls=(\"foo\", \"bar\")), \"item 1\", \"item 2\", \"item 3\"]])\n```\n\nNote that we cannot use the reserved `class` keyword, instead use `cls` to get a `class` attribute.\n\n```xml\n<div>\n <ol class=\"foo bar\" id=\"1\">\n <li>item 1</li>\n <li>item 2</li>\n <li>item 3</li>\n </ol>\n</div>\n```\n\n\n### Expand nodes\n\nIn some cases you may want to use the `mu.expand` function to only expand object nodes to a straightforward data structure.\n\n```python\nfrom mu import expand\n\nexpand([\"div\", [OL(), {\"class\": (\"foo\", \"bar\")}, \"item 1\", \"item 2\", \"item 3\"]])\n```\n\n```python\n[\"div\",\n [\"ol\", {\"class\": (\"foo\", \"bar\")},\n [\"li\", \"item 1\"],\n [\"li\", \"item 2\"],\n [\"li\", \"item 3\"]]]\n```\n\n\n### Serializing Python data structures\n\n```python\nmu.dumps([\"a\",True,3.0])\n```\n\n```python\nmu.loads(['_', {'as': 'array'},\n ['_', 'a'],\n ['_', {'as': 'boolean', 'value': 'true()'}],\n ['_', {'as': 'float'}, 3.0]])\n```\n\n```python\nmu.dumps(dict(a=\"a\",b=True,c=3.0))\n```\n\n```python\nmu.loads(['_', {'as': 'object'},\n ['a', 'a'],\n ['b', {'as': 'boolean', 'value': 'true()'}],\n ['c', {'as': 'float'}, 3.0]])\n```\n\nWhen `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.\n\n\n## Develop\n\n- Install [uv](https://github.com/astral-sh/uv).\n- `uv tool add ruff`\n- Maybe install `Ruff` VS Code extension\n\nRun linter.\n\n```shell\nuvx ruff check\n```\n\nRun formatter.\n\n```shell\nuvx ruff format\n```\n\n\nRun tests.\n\n```shell\nuv run pytest\n```\n\nOr with coverage and missing lines.\n\n```shell\nuv run pytest --cov-report term-missing --cov=mu\n```\n\n\n## Related work\n\n- [weavejester/hiccup](https://github.com/weavejester/hiccup)\n- [nbessi/pyhiccup](https://github.com/nbessi/pyhiccup)\n- [SXML](https://en.wikipedia.org/wiki/SXML)\n",
"bugtrack_url": null,
"license": null,
"summary": "Represent HTML and XML using Python data structures.",
"version": "0.1.3",
"project_urls": {
"GitHub": "https://github.com/Collage-CMS/mu"
},
"split_keywords": [
"html",
" templates",
" xml"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "21306fcdfdcbcbbffdb5fe336ef79f6b4b303d1f95c6f5aa7a1879bc8569aa5a",
"md5": "2c63e28125e5c90c8b533f13d3464aac",
"sha256": "92639696758ddd0eee9576004ad386aed58df374d9a6911e1670e9e55fa99751"
},
"downloads": -1,
"filename": "mu_xml-0.1.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2c63e28125e5c90c8b533f13d3464aac",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 9606,
"upload_time": "2025-08-18T18:17:35",
"upload_time_iso_8601": "2025-08-18T18:17:35.597615Z",
"url": "https://files.pythonhosted.org/packages/21/30/6fcdfdcbcbbffdb5fe336ef79f6b4b303d1f95c6f5aa7a1879bc8569aa5a/mu_xml-0.1.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "98a188cc3261420a1488ce93ea1449cdf3b271b28b551e6f96c35338a965fcc1",
"md5": "7f438d6ce579ac6a1a37d4c33da45a41",
"sha256": "cecb0f81175a962fe48b75e7cf92b147a4381aed90af6516365a4972754f8af8"
},
"downloads": -1,
"filename": "mu_xml-0.1.3.tar.gz",
"has_sig": false,
"md5_digest": "7f438d6ce579ac6a1a37d4c33da45a41",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 29336,
"upload_time": "2025-08-18T18:17:36",
"upload_time_iso_8601": "2025-08-18T18:17:36.853924Z",
"url": "https://files.pythonhosted.org/packages/98/a1/88cc3261420a1488ce93ea1449cdf3b271b28b551e6f96c35338a965fcc1/mu_xml-0.1.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-18 18:17:36",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Collage-CMS",
"github_project": "mu",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "mu-xml"
}