diff --git a/.gitignore b/.gitignore index ba0430d..e482470 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__/ \ No newline at end of file +__pycache__/ +*.egg-info/ \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e6f9099 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1683014792, + "narHash": "sha256-6Va9iVtmmsw4raBc3QKvQT2KT/NGRWlvUlJj46zN8B8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1a411f23ba299db155a5b45d5e145b85a7aafc42", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6383574 --- /dev/null +++ b/flake.nix @@ -0,0 +1,13 @@ +{ + inputs = { + nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; + flake-utils.url = github:numtide/flake-utils; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: { + defaultPackage = (import ./default.nix) { pkgs = nixpkgs.legacyPackages.${system}; }; + }); + + nixConfig.bash-prompt-prefix = "\\e\[94;1m[xopp2py]\\e\[m "; +} \ No newline at end of file diff --git a/src/xopp2oml/README.md b/src/xopp2oml/README.md index 34a1eed..114940f 100644 --- a/src/xopp2oml/README.md +++ b/src/xopp2oml/README.md @@ -2,4 +2,4 @@ This exporter generates OML descriptions that use the following vocabulary: -https://msdl.uantwerpen.be/git/lucasalbertins/DTDesign/src/d070f9e9e932ad9910eaab92eabc368e7ee96058/examples/oml/CDF/src/oml/flandersmake.be/cdf/vocabulary/xopp.oml \ No newline at end of file +https://msdl.uantwerpen.be/git/lucasalbertins/DTDesign/src/15c791c78589f9c064fc402feec5228b5a80f549/examples/oml/CDF/src/oml/flandersmake.be/cdf/vocabulary/xopp.oml \ No newline at end of file diff --git a/src/xopp2oml/template.oml b/src/xopp2oml/template.oml index afd77db..3c3076c 100644 --- a/src/xopp2oml/template.oml +++ b/src/xopp2oml/template.oml @@ -1,68 +1,69 @@ -{%- 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}} : xopp:XMLAttribute [ - xopp:hasKey "{{key}}" - xopp:hasValue "{{value}}" - xopp: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}} : xopp:{{el.__class__.__name__}} [ - xopp:hasText "{{el.text}}" - xopp: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}} : xopp:Layer [ - xopp: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}} : xopp:Page [ - xopp:hasWidth {{ page.width }} - xopp:hasHeight {{ page.height }} - xopp:hasBackgroundType "{{ page.background_type }}" - xopp:hasBackgroundColor "{{ page.background_color }}" - xopp:hasBackgroundStyle "{{ page.background_style }}" - xopp: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 as {{namespace}} { +{%- if minimal %} +// Minimal mode enabled: All XML attributes and non-text nodes are omitted. +{%- endif %} - uses as xopp - uses as object_diagram +description <{{output_namespace}}#> as {{shorthand}} { - ci model : xopp:Model [] + uses <{{namespaces.xopp}}#> as xopp + uses <{{namespaces.object_diagram}}#> as object_diagram + {%- if not minimal %} + uses <{{namespaces.dict}}#> as dict + {%- endif %} - ci file : xopp:File [ - xopp:hasCreator "{{ file.creator }}" + ci model : xopp:Model [ + object_diagram:hasName "{{shorthand}}" + xopp:hasCreator {{ file.creator | to_oml_string_literal }} xopp:hasFileVersion {{ file.fileversion }} - xopp:hasTitle "{{ file.title }}" + xopp:hasTitle {{ file.title | to_oml_string_literal }} {%- if file.preview != None %} - xopp:hasPreview "{{ toBase64(file.preview).decode('utf-8') }}" + xopp:hasPreview {{ file.preview | to_base64_string }} {%- endif %} + ] + + {%- for page_index, page in enumerate(file.pages) %} + + ci p{{page_index}} : xopp:Page [ + xopp:hasWidth {{ page.width }} + xopp:hasHeight {{ page.height }} + xopp:hasBackgroundType {{ page.background_type | to_oml_string_literal }} + xopp:hasBackgroundColor {{ page.background_color | to_oml_string_literal }} + xopp:hasBackgroundStyle {{ page.background_style | to_oml_string_literal }} + xopp:inFile model object_diagram:inModel model ] - {{ pages(file) }} + {%- for layer_index, layer in enumerate(page.layers) %} + + ci p{{page_index}}l{{layer_index}} : xopp:Layer [ + xopp:inPage p{{page_index}} + object_diagram:inModel model + ] + + {%- for el_index, el in enumerate(layer.elements) %} + {%- if not minimal or el.__class__.__name__ == "Text" and el.text != None %} + + ci p{{page_index}}l{{layer_index}}e{{el_index}} : xopp:{{el.__class__.__name__}} [ + xopp:hasText {{el.text | to_oml_string_literal}} + xopp:inLayer p{{page_index}}l{{layer_index}} + object_diagram:inModel model + ] + + {%- if not minimal %} + {%- for key, value in el.attributes.items() %} + + ci p{{page_index}}l{{layer_index}}e{{el_index}}a{{loop.index}} : xopp:XMLAttribute [ + dict:hasKey {{key | to_oml_string_literal}} + dict:hasValue {{value | to_oml_string_literal}} + xopp:ofLayerElement p{{page_index}}l{{layer_index}}e{{el_index}} + object_diagram:inModel model + ] + {%- endfor %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- endfor %} + {%- endfor %} } diff --git a/src/xopp2oml/writer.py b/src/xopp2oml/writer.py index ad13c8f..758d541 100644 --- a/src/xopp2oml/writer.py +++ b/src/xopp2oml/writer.py @@ -1,18 +1,69 @@ from xopp2py import abstract_syntax import io +import base64 -def writeOML(xournalFile: abstract_syntax.XournalFile, inputfile:str, namespace:str, ostream: io.TextIOBase): +CDF_NAMESPACES = { + # namespace of generated description + "description": "http://flandersmake.be/cdf/description/my_xopp", + "shorthand": "my_xopp", + + # vocabulary namespaces + "xopp": "http://flandersmake.be/cdf/vocabulary/xopp", + "object_diagram": "http://flandersmake.be/cdf/vocabulary/object_diagram", + "dict": "http://flandersmake.be/cdf/vocabulary/dict", +} + +DTDESIGN_NAMESPACES = { + # namespace of generated description + "description": "http://ua.be/sdo2l/description/artifacts/my_xopp", + "shorthand": "my_xopp", + + # vocabulary namespaces + "xopp": "http://ua.be/sdo2l/vocabulary/formalisms/xopp", + "object_diagram": "http://ua.be/sdo2l/vocabulary/formalisms/object_diagram", + "dict": "http://ua.be/sdo2l/vocabulary/formalisms/dict", + + "artifacts": "http://ua.be/sdo2l/description/artifacts/", +} + +def to_oml_string_literal(python_string): + # Not sure which characters to escape in OML. + # At the very least, quotes should be escaped: + return '"' + python_string.replace('"', '\\"') + '"' + +def to_base64_string(bytes): + return '"' + base64.b64encode(bytes).decode('utf-8') + '"' + +def writeOML( + xournalFile: abstract_syntax.XournalFile, + inputfile:str, + model_name: str, + ostream: io.TextIOBase, + namespaces=DTDESIGN_NAMESPACES): + """ Write out a Xournal++ file as an OML description. + Parameters: + xournalFile: abstract syntax of a parsed .xopp file + inputfile: name of input file (only for documentation/traceability purposes - any value can be used here) + model_name: used to determine the namespace of the generated OML description + namespace: namespace of xopp-OML-vocabulary + ostream: output stream to write to + """ import jinja2 import os - import base64 environment = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) + environment.filters['to_oml_string_literal'] = to_oml_string_literal + environment.filters['to_base64_string'] = to_base64_string + template = environment.get_template("template.oml") for piece in template.generate( + minimal=True, file=xournalFile, - toBase64=base64.b64encode, inputfile=inputfile, - namespace=namespace): - ostream.write(piece) + enumerate=enumerate, + output_namespace=namespaces['artifacts']+model_name+"_xopp", + shorthand=model_name+"_xopp", + namespaces=namespaces): + ostream.write(piece)