Simplify abstract syntax further. Added OML exporter and test.
This commit is contained in:
parent
a84f473feb
commit
ea70d9278e
11 changed files with 155 additions and 29 deletions
|
|
@ -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
15
shell.nix
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -3,20 +3,14 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Background:
|
|
||||||
type: str # could be enum
|
|
||||||
color: str # could be class
|
|
||||||
style: str # could be enum
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Stroke:
|
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
|
attributes: dict[str,str] # just the XML attributes as encountered
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Text:
|
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
|
attributes: dict[str,str] # just the XML attributes as encountered
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -27,7 +21,11 @@ class Layer:
|
||||||
class Page:
|
class Page:
|
||||||
width: Decimal
|
width: Decimal
|
||||||
height: 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]
|
layers: list[Layer]
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -35,5 +33,5 @@ class XournalFile:
|
||||||
creator: str # e.g., "Xournal++ 1.1.2"
|
creator: str # e.g., "Xournal++ 1.1.2"
|
||||||
fileversion: int # e.g., 4
|
fileversion: int # e.g., 4
|
||||||
title: str # obscure feature
|
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]
|
pages: list[Page]
|
||||||
|
|
@ -6,7 +6,20 @@ if __name__ == "__main__":
|
||||||
argparser = argparse.ArgumentParser(
|
argparser = argparse.ArgumentParser(
|
||||||
description = "Python interface for Xournal++ (.xopp) files.")
|
description = "Python interface for Xournal++ (.xopp) files.")
|
||||||
argparser.add_argument('filename')
|
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
|
args = argparser.parse_args() # exits on error
|
||||||
|
|
||||||
from parser import parseFile
|
print(args)
|
||||||
print(parseFile(args.filename))
|
|
||||||
|
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)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import abstract_syntax
|
from xopp2py import abstract_syntax
|
||||||
|
|
||||||
def parseFile(path) -> abstract_syntax.XournalFile:
|
def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
"""Parse a .xopp file."""
|
"""Parse a .xopp file."""
|
||||||
|
|
@ -14,7 +14,7 @@ def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
elif element.tag == "stroke":
|
elif element.tag == "stroke":
|
||||||
elements.append(
|
elements.append(
|
||||||
abstract_syntax.Stroke(
|
abstract_syntax.Stroke(
|
||||||
values=element.text, attributes=element.attrib))
|
text=element.text, attributes=element.attrib))
|
||||||
else:
|
else:
|
||||||
raise Error("Unsupported tag:" + element.tag)
|
raise Error("Unsupported tag:" + element.tag)
|
||||||
elif event == "end":
|
elif event == "end":
|
||||||
|
|
@ -29,10 +29,9 @@ def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
for (event, element) in context:
|
for (event, element) in context:
|
||||||
if event == "start":
|
if event == "start":
|
||||||
if element.tag == "background":
|
if element.tag == "background":
|
||||||
background = abstract_syntax.Background(
|
background_type = element.get("type")
|
||||||
type=element.get("type"),
|
background_color = element.get("color")
|
||||||
color=element.get("color"),
|
background_style = element.get("style")
|
||||||
style=element.get("plain"))
|
|
||||||
elif element.tag == "layer":
|
elif element.tag == "layer":
|
||||||
layers.append(parseLayer(context))
|
layers.append(parseLayer(context))
|
||||||
else:
|
else:
|
||||||
|
|
@ -42,7 +41,9 @@ def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
return abstract_syntax.Page(
|
return abstract_syntax.Page(
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
background=background,
|
background_type=background_type,
|
||||||
|
background_color=background_color,
|
||||||
|
background_style=background_style,
|
||||||
layers=layers)
|
layers=layers)
|
||||||
|
|
||||||
def parseXournal(context):
|
def parseXournal(context):
|
||||||
|
|
@ -56,7 +57,10 @@ def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
title = element.text
|
title = element.text
|
||||||
elif element.tag == "preview":
|
elif element.tag == "preview":
|
||||||
import base64
|
import base64
|
||||||
|
if element.text != None:
|
||||||
preview = base64.b64decode(element.text)
|
preview = base64.b64decode(element.text)
|
||||||
|
else:
|
||||||
|
preview = None
|
||||||
elif element.tag == "page":
|
elif element.tag == "page":
|
||||||
pages.append(parsePage(element, context))
|
pages.append(parsePage(element, context))
|
||||||
else:
|
else:
|
||||||
|
|
@ -70,6 +74,8 @@ def parseFile(path) -> abstract_syntax.XournalFile:
|
||||||
pages=pages)
|
pages=pages)
|
||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
|
# with gzip.open(path, mode='rt') as f:
|
||||||
|
# print(f.read())
|
||||||
with gzip.open(path, mode='rt') as f:
|
with gzip.open(path, mode='rt') as f:
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
context = ElementTree.iterparse(f, events=["start", "end"])
|
context = ElementTree.iterparse(f, events=["start", "end"])
|
||||||
|
|
|
||||||
0
src/xopp2py_oml/__init__.py
Normal file
0
src/xopp2py_oml/__init__.py
Normal file
18
src/xopp2py_oml/oml_writer.py
Normal file
18
src/xopp2py_oml/oml_writer.py
Normal 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)
|
||||||
68
src/xopp2py_oml/oml_writer.template
Normal file
68
src/xopp2py_oml/oml_writer.template
Normal 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) }}
|
||||||
|
}
|
||||||
BIN
tests/data/SmallXournalFile.xopp
Normal file
BIN
tests/data/SmallXournalFile.xopp
Normal file
Binary file not shown.
BIN
tests/data/TwoHiddenLayers.xopp
Normal file
BIN
tests/data/TwoHiddenLayers.xopp
Normal file
Binary file not shown.
17
tests/test_xopp2py.py
Normal file
17
tests/test_xopp2py.py
Normal 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")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue