Browse Source

Added abstract syntax and parser.

Joeri Exelmans 2 years ago
commit
8e6a90ef01
6 changed files with 134 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 1 0
      README.md
  3. 0 0
      src/xopp2py/__init__.py
  4. 43 0
      src/xopp2py/abstract_syntax.py
  5. 12 0
      src/xopp2py/main.py
  6. 77 0
      src/xopp2py/parser.py

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+__pycache__/

+ 1 - 0
README.md

@@ -0,0 +1 @@
+Python interface to .xopp (Xournal++) files.

+ 0 - 0
src/xopp2py/__init__.py


+ 43 - 0
src/xopp2py/abstract_syntax.py

@@ -0,0 +1,43 @@
+# Abstract syntax of the concrete syntax of Xournal++
+
+from dataclasses import dataclass
+from decimal import Decimal
+
+@dataclass
+class Header:
+    creator: str     # e.g., "Xournal++ 1.1.2"
+    fileversion: int # e.g., 4
+
+@dataclass
+class Background:
+    type: str  # could be enum
+    color: str # could be class
+    style: str # could be enum
+
+@dataclass
+class Stroke:
+    values: str # Just the XML text in between the tags. Meaning: stroke positions.
+    attributes: str   # just the XML attributes as encountered
+
+@dataclass
+class Text:
+    text: str
+    attributes: dict[str,str] # just the XML attributes as encountered
+
+@dataclass
+class Layer:
+    elements: list[Text | Stroke]
+
+@dataclass
+class Page:
+    width: Decimal
+    height: Decimal
+    background: Background
+    layers: list[Layer]
+
+@dataclass
+class XournalFile:
+    header: Header
+    title: str      # obscure feature
+    preview: bytes  # PNG-encoded preview of the (first page) of the file
+    pages: list[Page]

+ 12 - 0
src/xopp2py/main.py

@@ -0,0 +1,12 @@
+# Command-line tool that demonstrates how to use this library.
+
+import argparse
+
+if __name__ == "__main__":
+    argparser = argparse.ArgumentParser(
+        description = "Python interface for Xournal++ (.xopp) files.")
+    argparser.add_argument('filename')
+    args = argparser.parse_args() # exits on error
+
+    from .parser import parseFile
+    print(parseFile(args.filename))

+ 77 - 0
src/xopp2py/parser.py

@@ -0,0 +1,77 @@
+import abstract_syntax
+
+def parseFile(path) -> abstract_syntax.XournalFile:
+    """Parse a .xopp file."""
+
+    def parseLayer(context):
+        elements = []
+        for (event, element) in context:
+            if event == "start":
+                if element.tag == "text":
+                    elements.append(
+                        abstract_syntax.Text(
+                            text=element.text, attributes=element.attrib))
+                elif element.tag == "stroke":
+                    elements.append(
+                        abstract_syntax.Stroke(
+                            values=element.text, attributes=element.attrib))
+                else:
+                    raise Error("Unsupported tag:" + element.tag)
+            elif event == "end":
+                if element.tag == "layer":
+                    return abstract_syntax.Layer(elements=elements)
+
+    def parsePage(element, context):
+        from decimal import Decimal
+        width = Decimal(element.get("width"))
+        height = Decimal(element.get("height"))
+        layers = []
+        for (event, element) in context:
+            if event == "start":
+                if element.tag == "background":
+                    background = abstract_syntax.Background(
+                        type=element.get("type"),
+                        color=element.get("color"),
+                        style=element.get("plain"))
+                elif element.tag == "layer":
+                    layers.append(parseLayer(context))
+                else:
+                    raise Error("Unsupported tag:" + element.tag)
+            elif event == "end":
+                if element.tag == "page":
+                    return abstract_syntax.Page(
+                        width=width,
+                        height=height,
+                        background=background,
+                        layers=layers)
+
+    def parseXournal(context):
+        pages = []
+        for (event, element) in context:
+            if event == "start":
+                if element.tag == "xournal":
+                    header = abstract_syntax.Header(
+                        creator=element.get("creator"),
+                        fileversion=int(element.get("fileversion")),
+                    )
+                elif element.tag == "title":
+                    title = element.text
+                elif element.tag == "preview":
+                    import base64
+                    preview = base64.b64decode(element.text)
+                elif element.tag == "page":
+                    pages.append(parsePage(element, context))
+                else:
+                    raise Error("Unsupported tag:" + element.tag)
+
+        return abstract_syntax.XournalFile(
+            header=header,
+            title=title,
+            preview=preview,
+            pages=pages)
+
+    import gzip
+    with gzip.open(path, mode='rt') as f:
+        from xml.etree import ElementTree
+        context = ElementTree.iterparse(f, events=["start", "end"])
+        return parseXournal(context)