xournalpp-relative-background/relative-xopp-background

184 lines
6.4 KiB
Plaintext
Raw Normal View History

2021-09-22 22:10:35 +02:00
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import gzip
import traceback
from os import getenv
from pathlib import Path
from sys import argv, stderr
from typing import List
from xml.etree import ElementTree
from datetime import datetime
from xml.etree.ElementTree import Element
# Set to true if notify2 was successfully imported and initialized
NOTIFY2 = False
MSG_FALLBACK_STR = 'Messages will only be printed to stdout.'
try:
# DEPENDENCIES
# -> https://pypi.org/project/notify2/
# -> https://pypi.org/project/dbus-python/
import notify2
notify2.init('Relative Xournal++ Background Paths')
NOTIFY2 = True
except ImportError as e:
print(f'Optional dependency "notify2" is not available. {MSG_FALLBACK_STR}')
except Exception as e:
2021-09-26 11:44:54 +02:00
print(f'Error during import or initialization of notify2. '
f'Is the dependency "dbus-python" missing? {MSG_FALLBACK_STR}')
2021-09-22 22:10:35 +02:00
def main():
2021-09-26 11:44:54 +02:00
modified_files = 0
2021-09-22 22:10:35 +02:00
xopp_files: List[Path] = []
try:
xopp_files = parse_args()
for xopp_file in xopp_files:
modified_files += save_relative_paths(xopp_file=xopp_file)
# Catch exception, print it to stderr and save as file
except Exception as ex:
traceback.print_exc()
log_file = Path.home().joinpath('relative_paths.error')
with open(file=log_file, mode='w') as file:
traceback.print_exc(file=file)
notify(summary='Exception', message=getattr(ex, 'message', str(ex)))
exit(1)
notify(summary='Success!', message=f'Modified {modified_files} out of {len(xopp_files)} files.')
def parse_args() -> List[Path]:
NAUTILUS_SELECTION = 'NAUTILUS_SCRIPT_SELECTED_FILE_PATHS'
SCRIPT_NAME = 'relative-xopp-background'
selected_files_str = getenv(NAUTILUS_SELECTION)
if selected_files_str:
selected_files = selected_files_str.split('\n')
if len(selected_files[-1]) < 1:
selected_files = selected_files[:-1]
return [Path(file) for file in selected_files]
if len(argv) < 2:
usage_str = f'Usage:\n' \
f'\t{SCRIPT_NAME} <xopp-file-1> [<xopp-file-2>] [...]\n' \
f'\tenv {NAUTILUS_SELECTION}=<newline-delimited-list-of-xopp-files> {SCRIPT_NAME}'
print(usage_str, file=stderr)
raise Exception(usage_str)
return [Path(file) for file in argv[1:]]
def save_relative_paths(xopp_file: Path) -> int:
"""
Xournal++ stores XML content into gz compressed `.xopp` files.
This method modifies the content a given `.xopp` file "xopp_file":
In case there are PDF background files with an absolute path and they do
not exist at that location but instead reside next to the "xopp_file"
(located in the same directory), then their path is replaced with a relative one.
:returns the number of modified .xopp files (0 or 1)
"""
xml_modified = False
print(f'=== RELATIVE PATHS ===\nxopp_file: {xopp_file}')
if not xopp_file.exists():
raise Exception(f'The given file does not exist: {xopp_file}')
if not xopp_file.suffix == '.xopp':
raise Exception(f'Expected a Xournal++ file ending in .xopp: {xopp_file.suffix}')
# Open gz-compressed file
with gzip.open(xopp_file, mode="rt", encoding="utf-8") as fi:
xopp_str = fi.read()
root: Element = ElementTree.fromstring(xopp_str)
#
# xml_file: Path = Path('2019_SozialeMedien_Umgang-mit-Social-Media.xopp.xml')
# tree: ElementTree = ElementTree.parse(xml_file)
# root: Element = tree.getroot()
if not root.tag == 'xournal':
raise Exception(f'Unexpected root element: {root.tag}')
2021-09-26 11:44:54 +02:00
page: Element
for page in root:
if page.tag == 'page':
background: Element
for background in page:
if background.tag == 'background' \
and 'type' in background.attrib \
and background.attrib['type'] == 'pdf' \
and 'domain' in background.attrib \
and background.attrib['domain'] == 'absolute' \
and 'filename' in background.attrib:
print(f'type: {background.tag}, attrib: {background.attrib}')
abs_pdf: Path = Path(background.attrib['filename'])
2021-09-22 22:10:35 +02:00
if not abs_pdf.is_absolute():
continue # No action required as the path is already relative
if abs_pdf.exists():
continue # No action required as the background pdf will be found by Xournal++
if len(abs_pdf.name) < 1:
raise Exception(f'Expected the PDF file name to be non empty: {abs_pdf}')
rel_pdf: Path = xopp_file.parent.joinpath(abs_pdf.name)
if rel_pdf.exists():
# Replace absolute with relative path
2021-09-26 11:44:54 +02:00
background.attrib['filename'] = abs_pdf.name
2021-09-22 22:10:35 +02:00
xml_modified = True
else:
print("The PDF file was neither found at it's absolute path"
f" nor in the same directory as this Xournal++ file: {abs_pdf}")
if not xml_modified:
print('No modifications.')
return 0
print('The xml was modified!')
# Current date as string including milliseconds
date_str = datetime.today().strftime('%Y-%m-%d_%H-%M-%S_%f')[:-3]
xopp_file: Path
# Optional: Backup old .xopp file
# xopp_file.rename(xopp_file.parent.joinpath(xopp_file.name + '.' + date_str + '.backup'))
# Open in binary mode
encoding = 'utf-8'
fo = gzip.open(filename=xopp_file, mode='wb')
# Write xml header
# Default xml encoding: us-ascii
# -> https://stackoverflow.com/a/28208190/6334421
fo.write(f'<?xml version="1.0" encoding="{encoding}" standalone="no"?>\n'.encode(encoding=encoding))
# Write xml content
fo.write(ElementTree.tostring(root, encoding=encoding, method='xml'))
fo.close()
return 1
def notify(summary: str, message: str) -> None:
"""
If notify2 is initialized, the user is informed of the given
message via a graphical dbus notification.
If notify2 is not available, the message is instead printed to stdout.
"""
if NOTIFY2:
n = notify2.Notification(summary=summary, message=message)
n.show()
else:
print(f'=== MESSAGE ===\n{summary}\n{message}\n')
if __name__ == '__main__':
main()