Repository: DavidVujic/python-hiccup

Issue:
Content (inner text) should be placed in the correct order
**Describe the bug**
Content is placed after any child elements of a html element. It should be able to use the position in the hiccup sequence.

**To Reproduce**
This test will fail:
```python
def test_order_of_items() -> None:
    """Assert that items of different types are ordered as expected."""
    data = ["h1", "some", ["span.pys", "<py>"], "jokes"]

    assert render(data) == '<h1>some<span class="pys">&lt;py&gt;</span>jokes</h1>'
```

The rendered output is 
```html
<h1><span class="pys">&lt;py&gt;</span>somejokes</h1>
```

**Expected behavior**
It should be possible to specify the content to be before any child elements.

Hints from issue comments:

Solution patch:
diff --git a/README.md b/README.md
index c659630..e62702a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 # Python Hiccup
 
-This project started out as a fun challenge, and now evolving into something that could be useful.
-
-Current status: _experimental_
+Python Hiccup is a library for representing HTML using plain Python data structures.
 
 [![CircleCI](https://dl.circleci.com/status-badge/img/gh/DavidVujic/python-hiccup/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/DavidVujic/python-hiccup/tree/main)
 
@@ -14,6 +12,8 @@ Current status: _experimental_
 This is a Python implementation of the Hiccup syntax. Python Hiccup is a library for representing HTML in Python.
 Using `list` or `tuple` to represent HTML elements, and `dict` to represent the element attributes.
 
+_This project started out as a fun coding challenge, and now evolving into something useful for Python Dev teams._
+
 ## Usage
 Create server side HTML using plain Python data structures.
 This library should also be possible to combine with PyScript, but I haven't tested that out yet.
diff --git a/pyproject.toml b/pyproject.toml
index d472fae..321a573 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [project]
 name = "python-hiccup"
-version = "0.2.0"
-description = "Add your description here"
+version = "0.3.0"
+description = "Python Hiccup is a library for representing HTML using plain Python data structures"
 readme = "README.md"
 requires-python = ">=3.10"
 dependencies = []
diff --git a/src/python_hiccup/html/core.py b/src/python_hiccup/html/core.py
index b58dc26..2ee9431 100644
--- a/src/python_hiccup/html/core.py
+++ b/src/python_hiccup/html/core.py
@@ -5,7 +5,7 @@
 from collections.abc import Mapping, Sequence
 from functools import reduce
 
-from python_hiccup.transform import transform
+from python_hiccup.transform import CONTENT_TAG, transform
 
 
 def _element_allows_raw_content(element: str) -> bool:
@@ -56,23 +56,28 @@ def _suffix(element_data: str) -> str:
     return "" if any(s in normalized for s in specials) else " /"
 
 
-def _to_html(tag: Mapping) -> list:
+def _is_content(element: str) -> bool:
+    return element == CONTENT_TAG
+
+
+def _to_html(tag: Mapping, parent: str = "") -> list:
     element = next(iter(tag.keys()))
     child = next(iter(tag.values()))
 
+    if _is_content(element):
+        return [_escape(str(child), parent)]
+
     attributes = reduce(_to_attributes, tag.get("attributes", []), "")
     bool_attributes = reduce(_to_bool_attributes, tag.get("boolean_attributes", []), "")
     element_attributes = attributes + bool_attributes
 
-    content = [_escape(str(c), element) for c in tag.get("content", [])]
-
-    matrix = [_to_html(c) for c in child]
+    matrix = [_to_html(c, element) for c in child]
     flattened: list = reduce(operator.iadd, matrix, [])
 
     begin = f"{element}{element_attributes}" if element_attributes else element
 
-    if flattened or content:
-        return [f"<{begin}>", *flattened, *content, f"</{element}>"]
+    if flattened:
+        return [f"<{begin}>", *flattened, f"</{element}>"]
 
     if _closing_tag(element):
         return [f"<{begin}>", f"</{element}>"]
diff --git a/src/python_hiccup/transform/__init__.py b/src/python_hiccup/transform/__init__.py
index f040478..328b5e2 100644
--- a/src/python_hiccup/transform/__init__.py
+++ b/src/python_hiccup/transform/__init__.py
@@ -1,5 +1,5 @@
 """Transform Hiccup syntax."""
 
-from python_hiccup.transform.core import transform
+from python_hiccup.transform.core import CONTENT_TAG, transform
 
-__all__ = ["transform"]
+__all__ = ["CONTENT_TAG", "transform"]
diff --git a/src/python_hiccup/transform/core.py b/src/python_hiccup/transform/core.py
index 6587664..1561864 100644
--- a/src/python_hiccup/transform/core.py
+++ b/src/python_hiccup/transform/core.py
@@ -10,7 +10,8 @@
 ATTRIBUTES = "attributes"
 BOOLEAN_ATTRIBUTES = "boolean_attributes"
 CHILDREN = "children"
-CONTENT = "content"
+
+CONTENT_TAG = "<::HICCUP_CONTENT::>"
 
 
 def _is_attribute(item: Item) -> bool:
@@ -25,12 +26,6 @@ def _is_child(item: Item) -> bool:
     return isinstance(item, list | tuple)
 
 
-def _is_content(item: Item) -> bool:
-    pipeline = [_is_attribute, _is_boolean_attribute, _is_child]
-
-    return not any(fn(item) for fn in pipeline)
-
-
 def _is_sibling(item: Item) -> bool:
     return _is_child(item)
 
@@ -40,8 +35,6 @@ def _key_for_group(item: Item) -> str:
         return ATTRIBUTES
     if _is_boolean_attribute(item):
         return BOOLEAN_ATTRIBUTES
-    if _is_content(item):
-        return CONTENT
 
     return CHILDREN
 
@@ -67,6 +60,9 @@ def _extract_from_tag(tag: str) -> tuple[str, dict]:
 
 
 def _transform_tags(tags: Sequence) -> dict:
+    if not isinstance(tags, list | tuple):
+        return {CONTENT_TAG: tags}
+
     first, *rest = tags
 
     element, extracted = _extract_from_tag(first)
diff --git a/uv.lock b/uv.lock
index ac3b866..5b5447f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -103,7 +103,7 @@ wheels = [
 
 [[package]]
 name = "python-hiccup"
-version = "0.2.0"
+version = "0.3.0"
 source = { editable = "." }
 
 [package.dev-dependencies]


Test patch:
diff --git a/test/test_render_html.py b/test/test_render_html.py
index a172e10..dedfe5c 100644
--- a/test/test_render_html.py
+++ b/test/test_render_html.py
@@ -132,6 +132,13 @@ def test_generates_an_element_with_children() -> None:
 
 def test_allows_numeric_values_in_content() -> None:
     """Assert that numeric values are allowed as the content of an element."""
-    data = ["ul", ["li", 1]]
+    data = ["ul", ["li", 1], ["li", 2.2]]
 
-    assert render(data) == "<ul><li>1</li></ul>"
+    assert render(data) == "<ul><li>1</li><li>2.2</li></ul>"
+
+
+def test_order_of_items() -> None:
+    """Assert that items of different types are ordered as expected."""
+    data = ["h1", "some ", ["span.pys", "<py>"]]
+
+    assert render(data) == '<h1>some <span class="pys">&lt;py&gt;</span></h1>'