phml.core.formats.xml_format

  1from copy import deepcopy
  2from pathlib import Path
  3from re import match, sub
  4from typing import Optional
  5
  6from defusedxml.ElementTree import fromstring
  7
  8from phml.core.nodes import AST, PI, NODE, Element, Root, Text
  9from phml.core.virtual_python import VirtualPython
 10from phml.utilities import remove_nodes
 11
 12from .compile import ASTRenderer, apply_conditions, apply_python
 13from .format import Format
 14
 15__all__ = ["XMLFormat"]
 16
 17
 18def tag(element) -> str:
 19    """Parse the element tag from the xml tag.
 20
 21    Example:
 22        `{http://www.sitemaps.org/schemas/sitemap/0.9}url`
 23        yields
 24        `url`
 25    """
 26    return sub(r"\{[^}]+\}", "", element.tag)
 27
 28
 29def namespace(element) -> str:
 30    """Get the xmlns value from the tag prefix."""
 31    xmlns = match(r"\{([^}]+)\}", element.tag)
 32    return xmlns.group(1) or ""
 33
 34
 35def construct_element(element):
 36    """Construct a phml element from a xml element."""
 37
 38    current = Element(tag(element), {**element.attrib})
 39
 40    if element.text is not None and element.text.strip() != "":
 41        current.append(Text(element.text))
 42
 43    if len(element) > 0:
 44        for child in element:
 45            current.append(construct_element(child))
 46
 47    return current
 48
 49
 50class XMLFormat(Format):
 51    """Logic for parsing and compiling html files."""
 52
 53    extension: str = "xml"
 54
 55    @classmethod
 56    def parse(cls, data: str) -> str:
 57        if isinstance(data, Path):
 58            with open(data, "r", encoding="utf-8") as file:
 59                data = file.read()
 60
 61        if isinstance(data, str):
 62            root = fromstring(data)
 63            _namespace = namespace(root)
 64            root = AST(Root(children=[construct_element(root)]))
 65
 66            if _namespace != "":
 67                root.children[0]["xmlns"] = _namespace
 68
 69            return root
 70        raise Exception("Data passed into XMLFormat.parse must be either str or pathlib.Path")
 71
 72    @classmethod
 73    def compile(
 74        cls,
 75        ast: AST,
 76        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 77        **kwargs,
 78    ) -> AST:
 79        """Compile and process the given ast and return the resulting ast."""
 80
 81        attribs = {
 82            "version": kwargs.pop("version", None) or "1.0",
 83            "encoding": kwargs.pop("encoding", None) or "UTF-8",
 84        }
 85
 86        ast.tree.insert(0, PI("xml", attribs))
 87
 88        src = deepcopy(ast)
 89
 90        # 3. Search each element and find @if, @elif, and @else
 91        #    - Execute those statements
 92
 93        apply_conditions(src, VirtualPython(), **kwargs)
 94
 95        # 4. Search for python blocks and process them.
 96
 97        apply_python(src, VirtualPython(), **kwargs)
 98        remove_nodes(src, {"tag": "slot"})
 99
100        return src
101
102    @classmethod
103    def render(
104        cls,
105        ast: AST,
106        components: Optional[dict[str, dict[str, list | NODE]]] = None,
107        indent: int = 2,
108        **kwargs,
109    ) -> str:
110        indent = indent or 2
111
112        attribs = {
113            "version": kwargs.pop("version", None) or "1.0",
114            "encoding": kwargs.pop("encoding", None) or "UTF-8",
115        }
116
117        ast.tree.insert(0, PI("xml", attribs))
118
119        src = deepcopy(ast)
120
121        # 3. Search each element and find @if, @elif, and @else
122        #    - Execute those statements
123
124        apply_conditions(src, VirtualPython(), components={}, **kwargs)
125
126        # 4. Search for python blocks and process them.
127
128        apply_python(src, VirtualPython(), **kwargs)
129        remove_nodes(src, {"tag": "slot"})
130
131        return ASTRenderer(src, indent).compile(include_doctype=False)
class XMLFormat(phml.core.formats.format.Format):
 51class XMLFormat(Format):
 52    """Logic for parsing and compiling html files."""
 53
 54    extension: str = "xml"
 55
 56    @classmethod
 57    def parse(cls, data: str) -> str:
 58        if isinstance(data, Path):
 59            with open(data, "r", encoding="utf-8") as file:
 60                data = file.read()
 61
 62        if isinstance(data, str):
 63            root = fromstring(data)
 64            _namespace = namespace(root)
 65            root = AST(Root(children=[construct_element(root)]))
 66
 67            if _namespace != "":
 68                root.children[0]["xmlns"] = _namespace
 69
 70            return root
 71        raise Exception("Data passed into XMLFormat.parse must be either str or pathlib.Path")
 72
 73    @classmethod
 74    def compile(
 75        cls,
 76        ast: AST,
 77        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 78        **kwargs,
 79    ) -> AST:
 80        """Compile and process the given ast and return the resulting ast."""
 81
 82        attribs = {
 83            "version": kwargs.pop("version", None) or "1.0",
 84            "encoding": kwargs.pop("encoding", None) or "UTF-8",
 85        }
 86
 87        ast.tree.insert(0, PI("xml", attribs))
 88
 89        src = deepcopy(ast)
 90
 91        # 3. Search each element and find @if, @elif, and @else
 92        #    - Execute those statements
 93
 94        apply_conditions(src, VirtualPython(), **kwargs)
 95
 96        # 4. Search for python blocks and process them.
 97
 98        apply_python(src, VirtualPython(), **kwargs)
 99        remove_nodes(src, {"tag": "slot"})
100
101        return src
102
103    @classmethod
104    def render(
105        cls,
106        ast: AST,
107        components: Optional[dict[str, dict[str, list | NODE]]] = None,
108        indent: int = 2,
109        **kwargs,
110    ) -> str:
111        indent = indent or 2
112
113        attribs = {
114            "version": kwargs.pop("version", None) or "1.0",
115            "encoding": kwargs.pop("encoding", None) or "UTF-8",
116        }
117
118        ast.tree.insert(0, PI("xml", attribs))
119
120        src = deepcopy(ast)
121
122        # 3. Search each element and find @if, @elif, and @else
123        #    - Execute those statements
124
125        apply_conditions(src, VirtualPython(), components={}, **kwargs)
126
127        # 4. Search for python blocks and process them.
128
129        apply_python(src, VirtualPython(), **kwargs)
130        remove_nodes(src, {"tag": "slot"})
131
132        return ASTRenderer(src, indent).compile(include_doctype=False)

Logic for parsing and compiling html files.

XMLFormat()
extension: str = 'xml'

The extension or extensions for the file format. When writing to a file and extensions is a list then the first extensions in the list is used for the file extension.

@classmethod
def parse(cls, data: str) -> str:
56    @classmethod
57    def parse(cls, data: str) -> str:
58        if isinstance(data, Path):
59            with open(data, "r", encoding="utf-8") as file:
60                data = file.read()
61
62        if isinstance(data, str):
63            root = fromstring(data)
64            _namespace = namespace(root)
65            root = AST(Root(children=[construct_element(root)]))
66
67            if _namespace != "":
68                root.children[0]["xmlns"] = _namespace
69
70            return root
71        raise Exception("Data passed into XMLFormat.parse must be either str or pathlib.Path")

Parse the given data into a phml.core.nodes.AST.

 73    @classmethod
 74    def compile(
 75        cls,
 76        ast: AST,
 77        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 78        **kwargs,
 79    ) -> AST:
 80        """Compile and process the given ast and return the resulting ast."""
 81
 82        attribs = {
 83            "version": kwargs.pop("version", None) or "1.0",
 84            "encoding": kwargs.pop("encoding", None) or "UTF-8",
 85        }
 86
 87        ast.tree.insert(0, PI("xml", attribs))
 88
 89        src = deepcopy(ast)
 90
 91        # 3. Search each element and find @if, @elif, and @else
 92        #    - Execute those statements
 93
 94        apply_conditions(src, VirtualPython(), **kwargs)
 95
 96        # 4. Search for python blocks and process them.
 97
 98        apply_python(src, VirtualPython(), **kwargs)
 99        remove_nodes(src, {"tag": "slot"})
100
101        return src

Compile and process the given ast and return the resulting ast.

@classmethod
def render( cls, ast: phml.core.nodes.AST.AST, components: Optional[dict[str, dict[str, list | phml.core.nodes.nodes.Root | phml.core.nodes.nodes.Element | phml.core.nodes.nodes.Text | phml.core.nodes.nodes.Comment | phml.core.nodes.nodes.DocType | phml.core.nodes.nodes.Parent | phml.core.nodes.nodes.Node | phml.core.nodes.nodes.Literal]]] = None, indent: int = 2, **kwargs) -> str:
103    @classmethod
104    def render(
105        cls,
106        ast: AST,
107        components: Optional[dict[str, dict[str, list | NODE]]] = None,
108        indent: int = 2,
109        **kwargs,
110    ) -> str:
111        indent = indent or 2
112
113        attribs = {
114            "version": kwargs.pop("version", None) or "1.0",
115            "encoding": kwargs.pop("encoding", None) or "UTF-8",
116        }
117
118        ast.tree.insert(0, PI("xml", attribs))
119
120        src = deepcopy(ast)
121
122        # 3. Search each element and find @if, @elif, and @else
123        #    - Execute those statements
124
125        apply_conditions(src, VirtualPython(), components={}, **kwargs)
126
127        # 4. Search for python blocks and process them.
128
129        apply_python(src, VirtualPython(), **kwargs)
130        remove_nodes(src, {"tag": "slot"})
131
132        return ASTRenderer(src, indent).compile(include_doctype=False)

Compile the given phml.core.nodes.AST into string of a given format.