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