mirror of
https://codeberg.org/privacy1st/xournalpp-relative-background
synced 2024-12-04 22:25:02 +01:00
add script
This commit is contained in:
parent
4103ccf9f7
commit
3e5bff3d78
10
README.md
10
README.md
@ -0,0 +1,10 @@
|
||||
# Relative Xournal++ background paths
|
||||
|
||||
Replace absolute paths of Xournal++ PDF backgrounds with relative ones.
|
||||
|
||||
## Installation
|
||||
|
||||
Place [relative-xopp-background](relative-xopp-background) in `~/.local/share/nautilus/scripts/relative-xopp-background` and make it executable.
|
||||
|
||||
If missing, install [notify2](https://pypi.org/project/notify2/) and (optionally)
|
||||
[dbus-python](https://pypi.org/project/dbus-python/) with your package manager or with pip.
|
180
relative-xopp-background
Executable file
180
relative-xopp-background
Executable file
@ -0,0 +1,180 @@
|
||||
#!/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:
|
||||
print(f'Error during import or initialization of notify2. Is the dependency "dbus-python" missing? {MSG_FALLBACK_STR}')
|
||||
|
||||
|
||||
def main():
|
||||
modified_files=0
|
||||
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}')
|
||||
|
||||
child1: Element
|
||||
for child1 in root:
|
||||
if child1.tag == 'page':
|
||||
child2: Element
|
||||
for child2 in child1:
|
||||
if child2.tag == 'background' \
|
||||
and 'type' in child2.attrib \
|
||||
and child2.attrib['type'] == 'pdf' \
|
||||
and 'domain' in child2.attrib \
|
||||
and child2.attrib['domain'] == 'absolute' \
|
||||
and 'filename' in child2.attrib:
|
||||
print(f'type: {child2.tag}, attrib: {child2.attrib}')
|
||||
abs_pdf: Path = Path(child2.attrib['filename'])
|
||||
|
||||
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
|
||||
child2.attrib['filename'] = abs_pdf.name
|
||||
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()
|
Loading…
Reference in New Issue
Block a user