diff --git a/fgs/pandoc.py b/fgs/pandoc.py index 9fb7a6eaf8ab455c947bd785031e88a0c0b0fc78..37b24ef436d0e3396eb5049b2fd66d0af9366f09 100644 --- a/fgs/pandoc.py +++ b/fgs/pandoc.py @@ -37,15 +37,18 @@ def run_pandoc(source, factories, lang, base="markdown", extensions=[], extra_ar if json.dumps(out_dict["pandoc-api-version"]) != "[1, 22, 2]": raise Exception("Unsupported pandoc-api version", out_dict["pandoc-api-version"]) + custom_syntax_handler = CustomSyntaxHandler(factories, lang, custom_syntax_register) + # parse blocks raw_blocks = out_dict['blocks'] blocks = [] for raw_block in raw_blocks: #print('raw_block: ', type(raw_block), raw_block) - block = parse_from_register(factories, lang, block_parsing_register, raw_block) + block = parse_from_register(factories, lang, block_parsing_register, raw_block, custom_syntax_handler) if block != None: blocks.append(block) + contentmetadata = {} contentmetadata["toc_list"] = [] contentmetadata["toc_count"] = 0 @@ -57,7 +60,7 @@ def run_pandoc(source, factories, lang, base="markdown", extensions=[], extra_ar return (blocks, contentmetadata) -def parse_from_register(factories, lang, reg: dict, h: dict): +def parse_from_register(factories, lang, reg: dict, h: dict, custom_syntax_handler): t = h['t'] # pandoc type if t not in reg: raise Exception("pandoc type not in register", t, h) @@ -75,13 +78,16 @@ def parse_from_register(factories, lang, reg: dict, h: dict): return None handler = entry['handler'] - res = handler(factories, lang, entry['etype']) + res = handler(factories, lang, custom_syntax_handler, entry['etype']) else: handler = entry - res = handler(factories, lang) + res = handler(factories, lang, custom_syntax_handler) res.parse(c) + # Handle custom syntax + res = custom_syntax_handler.handle(res) + return res @@ -95,9 +101,10 @@ def parse_from_register(factories, lang, reg: dict, h: dict): # return super().default(obj) class Element(): - def __init__(self, factories, lang, etype = None): + def __init__(self, factories, lang, custom_syntax_handler, etype = None): self.factories = factories self.lang = lang + self.custom_syntax_handler = custom_syntax_handler if etype != None: self.etype = etype self.children = [] @@ -146,11 +153,11 @@ class Element(): return res def parse_block(self, raw_block): - res = parse_from_register(self.factories, self.lang, block_parsing_register, raw_block) + res = parse_from_register(self.factories, self.lang, block_parsing_register, raw_block, self.custom_syntax_handler) self.addChild(res) return res def parse_inline(self, raw_inline): - res = parse_from_register(self.factories, self.lang, inline_parsing_register, raw_inline) + res = parse_from_register(self.factories, self.lang, inline_parsing_register, raw_inline, self.custom_syntax_handler) self.addChild(res) return res @@ -539,6 +546,120 @@ class InlineRaw(Inline): # Format Text self.format = self.parse_text(pandocraw[0]) self.raw = self.parse_text(pandocraw[1]) +############################# CUSTOM SYNTAX #################################### + + +class CustomSyntaxHandler: + def __init__(self, factories, lang, custom_syntax_register): + self.factories = factories + self.lang = lang + self.register = custom_syntax_register + + # Executed after the parsing of every element. + # Returns either origelement or the new one to replace it. + def handle(self, origelement): + for curreg in self.register: + if self.run_tests(curreg, origelement): + #raise Exception("Found replacement!!!", curreg, origelement) + print("DEBUG: Found custom syntax!!!", curreg, origelement) + res = curreg["replace_with"](self.factories, self.lang, self) # create new replacement element + res.replace(origelement, curreg) # tell it what it is replacing + return res # return new replacement element + return origelement # no custom syntax detected + + def run_test(self, test, origelement): + if "key" not in test: + # This is just another test container. + return self.run_tests(test, origelement) + + tkey = test["key"] + if not hasattr(origelement, tkey): + raise Exception("Key not in Element:", tkey, origelement, test) + obj = getattr(origelement, tkey) + + ttype = test["type"] + + #print("DEBUG: Running test: ", ttype, "tkey:", tkey, "test:", test, "obj:", obj, "origelement:",origelement) + + if ttype == "str": # Test: str + if not isinstance(obj, str): + raise Exception("Object is not a string:", obj, type(obj), origelement, test) + + if "is" in test: + return test["is"] == obj + elif "contains" in test: + return test["contains"] in obj + else: + raise Exception("Don't know what to do with this test:", ttype, test, obj, origelement) + + if ttype == "list": # Test: list + if not isinstance(obj, list): + raise Exception("Object is not a list:", obj, type(obj), origelement, test) + + olen = len(obj) + if "len" in test and test["len"] != olen: + return False + if "len_min" in test and test["len_min"] > olen: + return False + if "len_max" in test and test["len_max"] < olen: + return False + + if olen == 0: + return False + + if "for_each" in test: + if olen == 0: + return False + tfor = test["for_each"] + for element in obj: + if not self.run_tests(tfor, element): + return False + if "first" in test: + if not self.run_tests(test["first"], obj[0]): + return False + if "last" in test: + if not self.run_tests(test["last"], obj[-1]): + return False + + return True + + else: # unknown Test + raise Exception("Unknown Test Type:", ttype, test) + + + def run_tests(self, testcontainer, origelement): + res = False + if "tests_all" in testcontainer: + res = self.run_tests_all(testcontainer["tests_all"], origelement) + elif "tests_any" in testcontainer: + res = self.run_tests_any(testcontainer["tests_any"], origelement) + else: + raise Exception("No tests found:", testcontainer, origelement) + #print("DEBUG: run_tests returned ",res," with ", testcontainer, " on ", origelement) + return res + + def run_tests_all(self, tests, origelement): + for test in tests: + if not self.run_test(test, origelement): + return False + return True + + def run_tests_any(self, tests, origelement): + for test in tests: + if self.run_test(test, origelement): + return True + return False + + + + +class CustomBlockTOC(Block): + etype = "toc" + def replace(self, origelement, custom_sytax_register_entry): + pass # TODO continue + + +############################## REGISTER ####################################### inline_parsing_register = { "Space" : InlineSpace, @@ -579,3 +700,29 @@ block_parsing_register = { #"Null" :{"type":"nothing" }, # TODO find file that triggers Null } +# Define custom syntax +custom_syntax_register = [ + # Jeder Eintrag in dieser Liste beschreibt eine custom syntax. Die gesamte Liste wird nach jedem parsen eines Elementes getestet. Falls die Tests erfolgreich sind, wird das Element mit 'replace_with' ersetzt. + { + # Table of Contents via a Paragraph containing '[TOC]' as its only content. + "replace_with": CustomBlockTOC, + # "tests_any": [] + "tests_all": [ + { "key": "eclass", "type": "str", "is": "block" }, + { "key": "etype", "type": "str", "is": "paragraph" }, + { "key": "content","type": "list", + "len": 1, + #"len_min": 1, + #"len_max": 1, + #"for_each": { "tests_all": [ ] }, + "first": { "tests_all": [ + { "key": "eclass", "type": "str", "is": "inline" }, + { "key": "etype", "type": "str", "is": "string" }, + { "key": "text", "type": "str", "is": "[TOC]" } + ]} + #"last": { "tests_all": [ ] }, + } + ] + } +] +