Added abstract syntax and parser.

This commit is contained in:
Joeri Exelmans 2023-03-01 17:58:05 +01:00
commit 8e6a90ef01
6 changed files with 134 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__/

1
README.md Normal file
View file

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

0
src/xopp2py/__init__.py Normal file
View file

View file

@ -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
src/xopp2py/main.py Normal file
View file

@ -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
src/xopp2py/parser.py Normal file
View file

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