mu-xml


Namemu-xml JSON
Version 0.1.3 PyPI version JSON
download
home_pageNone
SummaryRepresent HTML and XML using Python data structures.
upload_time2025-08-18 18:17:36
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseNone
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/>
&lt;foo&gt;
```

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&lt;foo&gt;\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"
}
        
Elapsed time: 1.30827s