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": [ ] },
+            }
+        ]
+    }
+]
+