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)
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.
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.
@classmethod
def
compile( 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, **kwargs) -> phml.core.nodes.AST.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.