diff options
Diffstat (limited to 'src/rub/xml.py')
-rw-r--r-- | src/rub/xml.py | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/rub/xml.py b/src/rub/xml.py new file mode 100644 index 0000000..ed61a8b --- /dev/null +++ b/src/rub/xml.py @@ -0,0 +1,70 @@ +# XML processing abstractions +# Copyright (C) 2023 Nguyễn Gia Phong +# +# This file is part of rub. +# +# Rub is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Rub is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with rub. If not, see <https://www.gnu.org/licenses/>. + +from copy import deepcopy +from functools import partial +from pathlib import Path + +from lxml.etree import QName, XML, XSLT, XSLTExtension + +__all__ = ['NS', 'generator', 'recurse'] + +NS = 'https://rub.parody' + + +def recurse(extension, context, input_node, output_parent): + """Apply template recursively on input node.""" + output = deepcopy(input_node) + for i in output: output.remove(i) + for i in input_node: + for j in extension.apply_templates(context, i): + if not isinstance(j, str): + output.append(deepcopy(j)) + elif len(output) == 0: + if output.text is None: + output.text = j + else: + output.text += j + elif output[-1].tail is None: + output[-1].tail = j + else: + output[-1].tail += j + output_parent.append(output) + + +class Evaluator(XSLTExtension): + def __init__(self, **handlers): + self.handlers = {QName(NS, k).text: v for k, v in handlers.items()} + super().__init__() + + def execute(self, context, self_node, input_node, output_parent): + handle = self.handlers.get(input_node.tag, recurse) + handle(self, context, input_node, output_parent) + + +def generator(xslt, **handlers): + """Return a function taking an XML file and apply given XSLT.""" + stylesheet = xslt.read_bytes() + extensions = {(NS, 'eval'): Evaluator(**handlers)} + transform = XSLT(XML(stylesheet), extensions=extensions) + + def make(src, dest): + dest.parent.mkdir(mode=0o755, parents=True, exist_ok=True) + dest.write_text(str(transform(XML(src.read_bytes())))) + + return make |