瀏覽代碼

Simplify abstract syntax further. Added OML exporter and test.

Joeri Exelmans 2 年之前
父節點
當前提交
ea70d9278e

+ 0 - 9
default.nix

@@ -1,9 +0,0 @@
-with import <nixpkgs> {};
-with pkgs.python3Packages;
-
-buildPythonPackage {
-  name = "xopp2py";
-  src = ./.;
-  format = "pyproject"; # tell Nix to use pyproject.toml
-  propagatedBuildInputs = [ hatchling ];
-}

+ 15 - 0
shell.nix

@@ -0,0 +1,15 @@
+# Generates a shell from where all the dependencies can be found.
+
+{ pkgs ? import <nixpkgs> {} }:
+
+let
+  SOURCE_DIR = builtins.toString ./src;
+in
+  pkgs.mkShell {
+    buildInputs = [
+      pkgs.python3
+      pkgs.python3Packages.jinja2
+    ];
+
+     PYTHONPATH = SOURCE_DIR;
+  }

+ 8 - 10
src/xopp2py/abstract_syntax.py

@@ -3,20 +3,14 @@
 from dataclasses import dataclass
 from decimal import Decimal
 
-@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.
+    text: str # Just the XML text in between the tags. Meaning: stroke positions.
     attributes: dict[str,str]   # just the XML attributes as encountered
 
 @dataclass
 class Text:
-    text: str
+    text: str # Just the XML text between the tags. Meaning: the text contents of the text element
     attributes: dict[str,str] # just the XML attributes as encountered
 
 @dataclass
@@ -27,7 +21,11 @@ class Layer:
 class Page:
     width: Decimal
     height: Decimal
-    background: Background
+
+    background_type: str  # could be enum
+    background_color: str # could be class
+    background_style: str # could be enum
+
     layers: list[Layer]
 
 @dataclass
@@ -35,5 +33,5 @@ class XournalFile:
     creator: str     # e.g., "Xournal++ 1.1.2"
     fileversion: int # e.g., 4
     title: str       # obscure feature
-    preview: bytes   # PNG-encoded preview of the (first page) of the file
+    preview: bytes | None   # PNG-encoded preview of the (first page) of the file
     pages: list[Page]

+ 15 - 2
src/xopp2py/main.py

@@ -6,7 +6,20 @@ if __name__ == "__main__":
     argparser = argparse.ArgumentParser(
         description = "Python interface for Xournal++ (.xopp) files.")
     argparser.add_argument('filename')
+    argparser.add_argument('--print-oml', action='store_true', help="Convert to OML and print")
+    argparser.add_argument('--write-oml', metavar='FILE', nargs=1, help="Convert to OML and write to file")
     args = argparser.parse_args() # exits on error
 
-    from parser import parseFile
-    print(parseFile(args.filename))
+    print(args)
+
+    from xopp2py import parser
+
+    asyntax = parser.parseFile(args.filename)
+    if args.print_oml:
+        from xopp2py_oml import oml_writer
+        import sys
+        oml_writer.writeOML(asyntax, args.filename, "my_xopp", sys.stdout)
+    elif args.write_oml != None:
+        from xopp2py_oml import oml_writer
+        with open(args.write_oml[0], 'wt') as f:
+            oml_writer.writeOML(asyntax, args.filename, "my_xopp", f)

+ 14 - 8
src/xopp2py/parser.py

@@ -1,4 +1,4 @@
-import abstract_syntax
+from xopp2py import abstract_syntax
 
 def parseFile(path) -> abstract_syntax.XournalFile:
     """Parse a .xopp file."""
@@ -14,7 +14,7 @@ def parseFile(path) -> abstract_syntax.XournalFile:
                 elif element.tag == "stroke":
                     elements.append(
                         abstract_syntax.Stroke(
-                            values=element.text, attributes=element.attrib))
+                            text=element.text, attributes=element.attrib))
                 else:
                     raise Error("Unsupported tag:" + element.tag)
             elif event == "end":
@@ -29,10 +29,9 @@ def parseFile(path) -> abstract_syntax.XournalFile:
         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"))
+                    background_type = element.get("type")
+                    background_color = element.get("color")
+                    background_style = element.get("style")
                 elif element.tag == "layer":
                     layers.append(parseLayer(context))
                 else:
@@ -42,7 +41,9 @@ def parseFile(path) -> abstract_syntax.XournalFile:
                     return abstract_syntax.Page(
                         width=width,
                         height=height,
-                        background=background,
+                        background_type=background_type,
+                        background_color=background_color,
+                        background_style=background_style,
                         layers=layers)
 
     def parseXournal(context):
@@ -56,7 +57,10 @@ def parseFile(path) -> abstract_syntax.XournalFile:
                     title = element.text
                 elif element.tag == "preview":
                     import base64
-                    preview = base64.b64decode(element.text)
+                    if element.text != None:
+                        preview = base64.b64decode(element.text)
+                    else:
+                        preview = None
                 elif element.tag == "page":
                     pages.append(parsePage(element, context))
                 else:
@@ -70,6 +74,8 @@ def parseFile(path) -> abstract_syntax.XournalFile:
             pages=pages)
 
     import gzip
+    # with gzip.open(path, mode='rt') as f:
+    #     print(f.read())
     with gzip.open(path, mode='rt') as f:
         from xml.etree import ElementTree
         context = ElementTree.iterparse(f, events=["start", "end"])

+ 0 - 0
src/xopp2py_oml/__init__.py


+ 18 - 0
src/xopp2py_oml/oml_writer.py

@@ -0,0 +1,18 @@
+from xopp2py import abstract_syntax
+import io
+
+def writeOML(xournalFile: abstract_syntax.XournalFile, inputfile:str, namespace:str, ostream: io.TextIOBase):
+    import jinja2
+    import os
+    import base64
+
+    environment = jinja2.Environment(
+        loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
+
+    template = environment.get_template("oml_writer.template")
+    for piece in template.generate(
+        file=xournalFile,
+        toBase64=base64.b64encode,
+        inputfile=inputfile,
+        namespace=namespace):
+        ostream.write(piece)

+ 68 - 0
src/xopp2py_oml/oml_writer.template

@@ -0,0 +1,68 @@
+{%- macro attributes(pageindex, page, layerindex, layer, elementindex, element) -%}
+{%- for key,value in element.attributes.items() %}
+  ci p{{pageindex}}l{{layerindex}}e{{elementindex}}a{{loop.index}} : xournalpp:XMLAttribute [
+    xournalpp:hasKey "{{key}}"
+    xournalpp:hasValue "{{value}}"
+    xournalpp:ofLayerElement p{{pageindex}}l{{layerindex}}e{{elementindex}}
+    object_diagram:inModel model
+  ]
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro elements(pageindex, page, layerindex, layer) -%}
+{% for el in layer.elements %}
+  ci p{{pageindex}}l{{layerindex}}e{{loop.index}} : xournalpp:{{el.__class__.__name__}} [
+    xournalpp:hasText "{{el.text}}"
+    xournalpp:inLayer p{{pageindex}}l{{layerindex}}
+    object_diagram:inModel model
+  ]
+  {{ attributes(pageindex, page, layerindex, layer, loop.index, el) -}}
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro layers(pageindex, page) -%}
+{% for layer in page.layers %}
+  ci p{{pageindex}}l{{loop.index}} : xournalpp:Layer [
+    xournalpp:inPage p{{pageindex}}
+    object_diagram:inModel model
+  ]
+  {{ elements(pageindex, page, loop.index, layer) -}}
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro pages(file) -%}
+{% for page in file.pages -%}
+  ci p{{loop.index}} : xournalpp:Page [
+    xournalpp:hasWidth {{ page.width }}
+    xournalpp:hasHeight {{ page.height }}
+    xournalpp:hasBackgroundType "{{ page.background_type }}"
+    xournalpp:hasBackgroundColor "{{ page.background_color }}"
+    xournalpp:hasBackgroundStyle "{{ page.background_style }}"
+    xournalpp:inFile file
+    object_diagram:inModel model
+  ]
+  {{ layers(loop.index, page) -}}
+{%- endfor %}
+{%- endmacro -%}
+// Warning: Generated code! Do not edit!
+// Input file: '{{inputfile}}'
+// Generator: https://msdl.uantwerpen.be/git/jexelmans/xopp2py
+description <http://flandersmake.be/cdf/description/{{namespace}}#> as {{namespace}} {
+
+  uses <http://flandersmake.be/cdf/vocabulary/xournalpp#> as xournalpp
+  uses <http://flandersmake.be/cdf/vocabulary/object_diagram#> as object_diagram
+
+  ci model : xournalpp:Model []
+
+  ci file : xournalpp:File [
+    xournalpp:hasCreator "{{ file.creator }}"
+    xournalpp:hasFileVersion {{ file.fileversion }}
+    xournalpp:hasTitle "{{ file.title }}"
+    {%- if file.preview != None %}
+    xournalpp:hasPreview "{{ toBase64(file.preview).decode('utf-8') }}"
+    {%- endif %}
+    object_diagram:inModel model
+  ]
+
+  {{ pages(file) }}
+}

二進制
tests/data/SmallXournalFile.xopp


二進制
tests/data/TwoHiddenLayers.xopp


+ 17 - 0
tests/test_xopp2py.py

@@ -0,0 +1,17 @@
+from xopp2py import parser, abstract_syntax
+from xopp2py_oml import oml_writer
+import os
+
+DATADIR = os.path.join(os.path.dirname(__file__), "data")
+
+class DummyOutput:
+    def write(self, text: str):
+        pass
+
+def parse(filename):
+    asyntax = parser.parseFile(os.path.join(DATADIR, filename))
+    oml_writer.writeOML(asyntax, filename, "my_xopp", DummyOutput())
+
+# Just see if these files parse without throwing an exception :)
+parse("SmallXournalFile.xopp")
+parse("TwoHiddenLayers.xopp")