Simplify abstract syntax further. Added OML exporter and test.

This commit is contained in:
Joeri Exelmans 2023-03-03 15:53:07 +01:00
parent a84f473feb
commit ea70d9278e
11 changed files with 155 additions and 29 deletions

View file

@ -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
shell.nix Normal file
View file

@ -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;
}

View file

@ -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]

View file

@ -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)

View file

@ -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"])

View file

View file

@ -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)

View file

@ -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) }}
}

Binary file not shown.

Binary file not shown.

17
tests/test_xopp2py.py Normal file
View file

@ -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")