aboutsummaryrefslogtreecommitdiff
path: root/pip-bins
diff options
context:
space:
mode:
Diffstat (limited to 'pip-bins')
-rwxr-xr-xpip-bins/csv2ods229
-rwxr-xr-xpip-bins/dumppdf.py468
-rwxr-xr-xpip-bins/odf2mht72
-rwxr-xr-xpip-bins/odf2xhtml59
-rwxr-xr-xpip-bins/odf2xml81
-rwxr-xr-xpip-bins/odfimgimport190
-rwxr-xr-xpip-bins/odflint216
-rwxr-xr-xpip-bins/odfmeta266
-rwxr-xr-xpip-bins/odfoutline144
-rwxr-xr-xpip-bins/odfuserfield101
-rwxr-xr-xpip-bins/pdf2txt.py324
-rwxr-xr-xpip-bins/pdfplumber8
-rwxr-xr-xpip-bins/pypdfium28
-rwxr-xr-xpip-bins/xml2odf241
14 files changed, 2407 insertions, 0 deletions
diff --git a/pip-bins/csv2ods b/pip-bins/csv2ods
new file mode 100755
index 0000000..c9ece9f
--- /dev/null
+++ b/pip-bins/csv2ods
@@ -0,0 +1,229 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Agustin Henze -> agustinhenze at gmail.com
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+# Søren Roug
+#
+# Oct 2014: Georges Khaznadar <georgesk@debian.org>
+# - ported to Python3
+# - imlemented the missing switch -c / --encoding, with an extra
+# feature for POSIX platforms which can guess encoding.
+
+from odf.opendocument import OpenDocumentSpreadsheet
+from odf.style import Style, TextProperties, ParagraphProperties, TableColumnProperties
+from odf.text import P
+from odf.table import Table, TableColumn, TableRow, TableCell
+from optparse import OptionParser
+import sys,csv,re, os, codecs
+
+if sys.version_info[0]==3: unicode=str
+
+if sys.version_info[0]==2:
+ class UTF8Recoder:
+ """
+ Iterator that reads an encoded stream and reencodes the input to UTF-8
+ """
+ def __init__(self, f, encoding):
+ self.reader = codecs.getreader(encoding)(f)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ return self.reader.next().encode("utf-8")
+
+ class UnicodeReader:
+ """
+ A CSV reader which will iterate over lines in the CSV file "f",
+ which is encoded in the given encoding.
+ """
+
+ def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
+ f = UTF8Recoder(f, encoding)
+ self.reader = csv.reader(f, dialect=dialect, **kwds)
+
+ def next(self):
+ row = self.reader.next()
+ return [unicode(s, "utf-8") for s in row]
+
+ def __iter__(self):
+ return self
+
+
+def csvToOds( pathFileCSV, pathFileODS, tableName='table',
+ delimiter=',', quoting=csv.QUOTE_MINIMAL,
+ quotechar = '"', escapechar = None,
+ skipinitialspace = False, lineterminator = '\r\n',
+ encoding="utf-8"):
+ textdoc = OpenDocumentSpreadsheet()
+ # Create a style for the table content. One we can modify
+ # later in the word processor.
+ tablecontents = Style(name="Table Contents", family="paragraph")
+ tablecontents.addElement(ParagraphProperties(numberlines="false", linenumber="0"))
+ tablecontents.addElement(TextProperties(fontweight="bold"))
+ textdoc.styles.addElement(tablecontents)
+
+ # Start the table
+ table = Table( name=tableName )
+
+ if sys.version_info[0]==3:
+ reader = csv.reader(open(pathFileCSV, encoding=encoding),
+ delimiter=delimiter,
+ quoting=quoting,
+ quotechar=quotechar,
+ escapechar=escapechar,
+ skipinitialspace=skipinitialspace,
+ lineterminator=lineterminator)
+ else:
+ reader = UnicodeReader(open(pathFileCSV),
+ encoding=encoding,
+ delimiter=delimiter,
+ quoting=quoting,
+ quotechar=quotechar,
+ escapechar=escapechar,
+ skipinitialspace=skipinitialspace,
+ lineterminator=lineterminator)
+ fltExp = re.compile('^\s*[-+]?\d+(\.\d+)?\s*$')
+
+ for row in reader:
+ tr = TableRow()
+ table.addElement(tr)
+ for val in row:
+ if fltExp.match(val):
+ tc = TableCell(valuetype="float", value=val.strip())
+ else:
+ tc = TableCell(valuetype="string")
+ tr.addElement(tc)
+ p = P(stylename=tablecontents,text=val)
+ tc.addElement(p)
+
+ textdoc.spreadsheet.addElement(table)
+ textdoc.save( pathFileODS )
+
+if __name__ == "__main__":
+ usage = "%prog -i file.csv -o file.ods -d"
+ parser = OptionParser(usage=usage, version="%prog 0.1")
+ parser.add_option('-i','--input', action='store',
+ dest='input', help='File input in csv')
+ parser.add_option('-o','--output', action='store',
+ dest='output', help='File output in ods')
+ parser.add_option('-d','--delimiter', action='store',
+ dest='delimiter', help='specifies a one-character string to use as the field separator. It defaults to ",".')
+
+ parser.add_option('-c','--encoding', action='store',
+ dest='encoding', help='specifies the encoding the file csv. It defaults to utf-8')
+
+ parser.add_option('-t','--table', action='store',
+ dest='tableName', help='The table name in the output file')
+
+ parser.add_option('-s','--skipinitialspace',
+ dest='skipinitialspace', help='''specifies how to interpret whitespace which
+ immediately follows a delimiter. It defaults to False, which
+ means that whitespace immediately following a delimiter is part
+ of the following field.''')
+
+ parser.add_option('-l','--lineterminator', action='store',
+ dest='lineterminator', help='''specifies the character sequence which should
+ terminate rows.''')
+
+ parser.add_option('-q','--quoting', action='store',
+ dest='quoting', help='''It can take on any of the following module constants:
+ 0 = QUOTE_MINIMAL means only when required, for example, when a field contains either the quotechar or the delimiter
+ 1 = QUOTE_ALL means that quotes are always placed around fields.
+ 2 = QUOTE_NONNUMERIC means that quotes are always placed around fields which do not parse as integers or floating point numbers.
+ 3 = QUOTE_NONE means that quotes are never placed around fields.
+ It defaults is QUOTE_MINIMAL''')
+
+ parser.add_option('-e','--escapechar', action='store',
+ dest='escapechar', help='''specifies a one-character string used to escape the delimiter when quoting is set to QUOTE_NONE.''')
+
+ parser.add_option('-r','--quotechar', action='store',
+ dest='quotechar', help='''specifies a one-character string to use as the quoting character. It defaults to ".''')
+
+ (options, args) = parser.parse_args()
+
+ if options.input:
+ pathFileCSV = options.input
+ else:
+ parser.print_help()
+ exit( 0 )
+
+ if options.output:
+ pathFileODS = options.output
+ else:
+ parser.print_help()
+ exit( 0 )
+
+ if options.delimiter:
+ delimiter = options.delimiter
+ else:
+ delimiter = ","
+
+ if options.skipinitialspace:
+ skipinitialspace = True
+ else:
+ skipinitialspace=False
+
+ if options.lineterminator:
+ lineterminator = options.lineterminator
+ else:
+ lineterminator ="\r\n"
+
+ if options.escapechar:
+ escapechar = options.escapechar
+ else:
+ escapechar=None
+
+ if options.tableName:
+ tableName = options.tableName
+ else:
+ tableName = "table"
+
+ if options.quotechar:
+ quotechar = options.quotechar
+ else:
+ quotechar = "\""
+
+ encoding = "utf-8" # default setting
+ ###########################################################
+ ## try to guess the encoding; this is implemented only with
+ ## POSIX platforms. Can it be improved?
+ output = os.popen('/usr/bin/file ' + pathFileCSV).read()
+ m=re.match(r'^.*: ([-a-zA-Z0-9]+) text$', output)
+ if m:
+ encoding=m.group(1)
+ if 'ISO-8859' in encoding:
+ encoding="latin-1"
+ else:
+ encoding="utf-8"
+ ############################################################
+ # when the -c or --coding switch is used, it takes precedence
+ if options.encoding:
+ encoding = options.encoding
+
+ csvToOds( pathFileCSV=unicode(pathFileCSV),
+ pathFileODS=unicode(pathFileODS),
+ delimiter=delimiter, skipinitialspace=skipinitialspace,
+ escapechar=escapechar,
+ lineterminator=unicode(lineterminator),
+ tableName=tableName, quotechar=quotechar,
+ encoding=encoding)
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/dumppdf.py b/pip-bins/dumppdf.py
new file mode 100755
index 0000000..c2066aa
--- /dev/null
+++ b/pip-bins/dumppdf.py
@@ -0,0 +1,468 @@
+#!/usr/bin/python3
+"""Extract pdf structure in XML format"""
+
+import logging
+import os.path
+import re
+import sys
+from argparse import ArgumentParser
+from collections.abc import Container, Iterable
+from typing import Any, TextIO, cast
+
+import pdfminer
+from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines, PDFXRefFallback
+from pdfminer.pdfexceptions import (
+ PDFIOError,
+ PDFObjectNotFound,
+ PDFTypeError,
+ PDFValueError,
+)
+from pdfminer.pdfpage import PDFPage
+from pdfminer.pdfparser import PDFParser
+from pdfminer.pdftypes import PDFObjRef, PDFStream, resolve1, stream_value
+from pdfminer.psparser import LIT, PSKeyword, PSLiteral
+from pdfminer.utils import isnumber
+
+logging.basicConfig()
+logger = logging.getLogger(__name__)
+
+ESC_PAT = re.compile(r'[\000-\037&<>()"\042\047\134\177-\377]')
+
+
+def escape(s: str | bytes) -> str:
+ us = str(s, "latin-1") if isinstance(s, bytes) else s
+ return ESC_PAT.sub(lambda m: f"&#{ord(m.group(0))};", us)
+
+
+def dumpxml(out: TextIO, obj: object, codec: str | None = None) -> None:
+ if obj is None:
+ out.write("<null />")
+ return
+
+ if isinstance(obj, dict):
+ out.write(f'<dict size="{len(obj)}">\n')
+ for k, v in obj.items():
+ out.write(f"<key>{k}</key>\n")
+ out.write("<value>")
+ dumpxml(out, v)
+ out.write("</value>\n")
+ out.write("</dict>")
+ return
+
+ if isinstance(obj, list):
+ out.write(f'<list size="{len(obj)}">\n')
+ for v in obj:
+ dumpxml(out, v)
+ out.write("\n")
+ out.write("</list>")
+ return
+
+ if isinstance(obj, (str, bytes)):
+ out.write(f'<string size="{len(obj)}">{escape(obj)}</string>')
+ return
+
+ if isinstance(obj, PDFStream):
+ if codec == "raw":
+ # Bug: writing bytes to text I/O. This will raise TypeError.
+ out.write(obj.get_rawdata()) # type: ignore [arg-type]
+ elif codec == "binary":
+ # Bug: writing bytes to text I/O. This will raise TypeError.
+ out.write(obj.get_data()) # type: ignore [arg-type]
+ else:
+ out.write("<stream>\n<props>\n")
+ dumpxml(out, obj.attrs)
+ out.write("\n</props>\n")
+ if codec == "text":
+ data = obj.get_data()
+ out.write(f'<data size="{len(data)}">{escape(data)}</data>\n')
+ out.write("</stream>")
+ return
+
+ if isinstance(obj, PDFObjRef):
+ out.write(f'<ref id="{obj.objid}" />')
+ return
+
+ if isinstance(obj, PSKeyword):
+ # Likely bug: obj.name is bytes, not str
+ out.write(f"<keyword>{obj.name}</keyword>") # type: ignore [str-bytes-safe]
+ return
+
+ if isinstance(obj, PSLiteral):
+ # Likely bug: obj.name may be bytes, not str
+ out.write(f"<literal>{obj.name}</literal>") # type: ignore [str-bytes-safe]
+ return
+
+ if isnumber(obj):
+ out.write(f"<number>{obj}</number>")
+ return
+
+ raise PDFTypeError(obj)
+
+
+def dumptrailers(
+ out: TextIO,
+ doc: PDFDocument,
+ show_fallback_xref: bool = False,
+) -> None:
+ for xref in doc.xrefs:
+ if not isinstance(xref, PDFXRefFallback) or show_fallback_xref:
+ out.write("<trailer>\n")
+ dumpxml(out, xref.get_trailer())
+ out.write("\n</trailer>\n\n")
+ no_xrefs = all(isinstance(xref, PDFXRefFallback) for xref in doc.xrefs)
+ if no_xrefs and not show_fallback_xref:
+ msg = (
+ "This PDF does not have an xref. Use --show-fallback-xref if "
+ "you want to display the content of a fallback xref that "
+ "contains all objects."
+ )
+ logger.warning(msg)
+
+
+def dumpallobjs(
+ out: TextIO,
+ doc: PDFDocument,
+ codec: str | None = None,
+ show_fallback_xref: bool = False,
+) -> None:
+ visited = set()
+ out.write("<pdf>")
+ for xref in doc.xrefs:
+ for objid in xref.get_objids():
+ if objid in visited:
+ continue
+ visited.add(objid)
+ try:
+ obj = doc.getobj(objid)
+ if obj is None:
+ continue
+ out.write(f'<object id="{objid}">\n')
+ dumpxml(out, obj, codec=codec)
+ out.write("\n</object>\n\n")
+ except PDFObjectNotFound as e:
+ print(f"not found: {e!r}")
+ dumptrailers(out, doc, show_fallback_xref)
+ out.write("</pdf>")
+
+
+def dumpoutline(
+ outfp: TextIO,
+ fname: str,
+ objids: Any,
+ pagenos: Container[int],
+ password: str = "",
+ dumpall: bool = False,
+ codec: str | None = None,
+ extractdir: str | None = None,
+) -> None:
+ with open(fname, "rb") as fp:
+ parser = PDFParser(fp)
+ doc = PDFDocument(parser, password)
+ pages = {
+ page.pageid: pageno
+ for (pageno, page) in enumerate(PDFPage.create_pages(doc), 1)
+ }
+
+ def resolve_dest(dest: object) -> Any:
+ if isinstance(dest, (str, bytes)):
+ dest = resolve1(doc.get_dest(dest))
+ elif isinstance(dest, PSLiteral):
+ dest = resolve1(doc.get_dest(dest.name))
+ if isinstance(dest, dict):
+ dest = dest["D"]
+ if isinstance(dest, PDFObjRef):
+ dest = dest.resolve()
+ return dest
+
+ try:
+ outlines = doc.get_outlines()
+ outfp.write("<outlines>\n")
+ for level, title, dest, a, _se in outlines:
+ pageno = None
+ if dest:
+ dest = resolve_dest(dest)
+ pageno = pages[dest[0].objid]
+ elif a:
+ action = a
+ if isinstance(action, dict):
+ subtype = action.get("S")
+ if subtype and repr(subtype) == "/'GoTo'" and action.get("D"):
+ dest = resolve_dest(action["D"])
+ pageno = pages[dest[0].objid]
+ s = escape(title)
+ outfp.write(f'<outline level="{level!r}" title="{s}">\n')
+ if dest is not None:
+ outfp.write("<dest>")
+ dumpxml(outfp, dest)
+ outfp.write("</dest>\n")
+ if pageno is not None:
+ outfp.write(f"<pageno>{pageno!r}</pageno>\n")
+ outfp.write("</outline>\n")
+ outfp.write("</outlines>\n")
+ except PDFNoOutlines:
+ pass
+ parser.close()
+
+
+LITERAL_FILESPEC = LIT("Filespec")
+LITERAL_EMBEDDEDFILE = LIT("EmbeddedFile")
+
+
+def extractembedded(fname: str, password: str, extractdir: str) -> None:
+ def extract1(objid: int, obj: dict[str, Any]) -> None:
+ filename = os.path.basename(obj.get("UF") or cast(bytes, obj.get("F")).decode())
+ fileref = obj["EF"].get("UF") or obj["EF"].get("F")
+ fileobj = doc.getobj(fileref.objid)
+ if not isinstance(fileobj, PDFStream):
+ error_msg = (
+ f"unable to process PDF: reference for {filename!r} is not a PDFStream"
+ )
+ raise PDFValueError(error_msg)
+ if fileobj.get("Type") is not LITERAL_EMBEDDEDFILE:
+ raise PDFValueError(
+ f"unable to process PDF: reference for {filename!r} "
+ "is not an EmbeddedFile",
+ )
+ path = os.path.join(extractdir, f"{objid:06d}-{filename}")
+ if os.path.exists(path):
+ raise PDFIOError(f"file exists: {path!r}")
+ print(f"extracting: {path!r}")
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "wb") as out:
+ out.write(fileobj.get_data())
+
+ with open(fname, "rb") as fp:
+ parser = PDFParser(fp)
+ doc = PDFDocument(parser, password)
+ extracted_objids = set()
+ for xref in doc.xrefs:
+ for objid in xref.get_objids():
+ obj = doc.getobj(objid)
+ if (
+ objid not in extracted_objids
+ and isinstance(obj, dict)
+ and obj.get("Type") is LITERAL_FILESPEC
+ ):
+ extracted_objids.add(objid)
+ extract1(objid, obj)
+
+
+def dumppdf(
+ outfp: TextIO,
+ fname: str,
+ objids: Iterable[int],
+ pagenos: Container[int],
+ password: str = "",
+ dumpall: bool = False,
+ codec: str | None = None,
+ extractdir: str | None = None,
+ show_fallback_xref: bool = False,
+) -> None:
+ with open(fname, "rb") as fp:
+ parser = PDFParser(fp)
+ doc = PDFDocument(parser, password)
+ if objids:
+ for objid in objids:
+ obj = doc.getobj(objid)
+ dumpxml(outfp, obj, codec=codec)
+ if pagenos:
+ for pageno, page in enumerate(PDFPage.create_pages(doc)):
+ if pageno in pagenos:
+ if codec:
+ for obj in page.contents:
+ obj = stream_value(obj)
+ dumpxml(outfp, obj, codec=codec)
+ else:
+ dumpxml(outfp, page.attrs)
+ if dumpall:
+ dumpallobjs(outfp, doc, codec, show_fallback_xref)
+ if (not objids) and (not pagenos) and (not dumpall):
+ dumptrailers(outfp, doc, show_fallback_xref)
+ if codec not in ("raw", "binary"):
+ outfp.write("\n")
+
+
+def create_parser() -> ArgumentParser:
+ parser = ArgumentParser(description=__doc__, add_help=True)
+ parser.add_argument(
+ "files",
+ type=str,
+ default=None,
+ nargs="+",
+ help="One or more paths to PDF files.",
+ )
+
+ parser.add_argument(
+ "--version",
+ "-v",
+ action="version",
+ version=f"pdfminer.six v{pdfminer.__version__}",
+ )
+ parser.add_argument(
+ "--debug",
+ "-d",
+ default=False,
+ action="store_true",
+ help="Use debug logging level.",
+ )
+ procedure_parser = parser.add_mutually_exclusive_group()
+ procedure_parser.add_argument(
+ "--extract-toc",
+ "-T",
+ default=False,
+ action="store_true",
+ help="Extract structure of outline",
+ )
+ procedure_parser.add_argument(
+ "--extract-embedded",
+ "-E",
+ type=str,
+ help="Extract embedded files",
+ )
+
+ parse_params = parser.add_argument_group(
+ "Parser",
+ description="Used during PDF parsing",
+ )
+ parse_params.add_argument(
+ "--page-numbers",
+ type=int,
+ default=None,
+ nargs="+",
+ help="A space-seperated list of page numbers to parse.",
+ )
+ parse_params.add_argument(
+ "--pagenos",
+ "-p",
+ type=str,
+ help="A comma-separated list of page numbers to parse. Included for "
+ "legacy applications, use --page-numbers for more idiomatic "
+ "argument entry.",
+ )
+ parse_params.add_argument(
+ "--objects",
+ "-i",
+ type=str,
+ help="Comma separated list of object numbers to extract",
+ )
+ parse_params.add_argument(
+ "--all",
+ "-a",
+ default=False,
+ action="store_true",
+ help="If the structure of all objects should be extracted",
+ )
+ parse_params.add_argument(
+ "--show-fallback-xref",
+ action="store_true",
+ help="Additionally show the fallback xref. Use this if the PDF "
+ "has zero or only invalid xref's. This setting is ignored if "
+ "--extract-toc or --extract-embedded is used.",
+ )
+ parse_params.add_argument(
+ "--password",
+ "-P",
+ type=str,
+ default="",
+ help="The password to use for decrypting PDF file.",
+ )
+
+ output_params = parser.add_argument_group(
+ "Output",
+ description="Used during output generation.",
+ )
+ output_params.add_argument(
+ "--outfile",
+ "-o",
+ type=str,
+ default="-",
+ help='Path to file where output is written. Or "-" (default) to '
+ "write to stdout.",
+ )
+ codec_parser = output_params.add_mutually_exclusive_group()
+ codec_parser.add_argument(
+ "--raw-stream",
+ "-r",
+ default=False,
+ action="store_true",
+ help="Write stream objects without encoding",
+ )
+ codec_parser.add_argument(
+ "--binary-stream",
+ "-b",
+ default=False,
+ action="store_true",
+ help="Write stream objects with binary encoding",
+ )
+ codec_parser.add_argument(
+ "--text-stream",
+ "-t",
+ default=False,
+ action="store_true",
+ help="Write stream objects as plain text",
+ )
+
+ return parser
+
+
+def main(argv: list[str] | None = None) -> None:
+ parser = create_parser()
+ args = parser.parse_args(args=argv)
+
+ if args.debug:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ objids = [int(x) for x in args.objects.split(",")] if args.objects else []
+
+ if args.page_numbers:
+ pagenos = {x - 1 for x in args.page_numbers}
+ elif args.pagenos:
+ pagenos = {int(x) - 1 for x in args.pagenos.split(",")}
+ else:
+ pagenos = set()
+
+ password = args.password
+
+ if args.raw_stream:
+ codec: str | None = "raw"
+ elif args.binary_stream:
+ codec = "binary"
+ elif args.text_stream:
+ codec = "text"
+ else:
+ codec = None
+
+ # Use context manager for file output, ensuring proper cleanup
+ with sys.stdout if args.outfile == "-" else open(args.outfile, "w") as outfp:
+ for fname in args.files:
+ if args.extract_toc:
+ dumpoutline(
+ outfp,
+ fname,
+ objids,
+ pagenos,
+ password=password,
+ dumpall=args.all,
+ codec=codec,
+ extractdir=None,
+ )
+ elif args.extract_embedded:
+ extractembedded(
+ fname, password=password, extractdir=args.extract_embedded
+ )
+ else:
+ dumppdf(
+ outfp,
+ fname,
+ objids,
+ pagenos,
+ password=password,
+ dumpall=args.all,
+ codec=codec,
+ extractdir=None,
+ show_fallback_xref=args.show_fallback_xref,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pip-bins/odf2mht b/pip-bins/odf2mht
new file mode 100755
index 0000000..e10e63b
--- /dev/null
+++ b/pip-bins/odf2mht
@@ -0,0 +1,72 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+from __future__ import print_function
+from odf.odf2xhtml import ODF2XHTML
+import zipfile
+import sys
+#from time import gmtime, strftime
+
+from email.mime.multipart import MIMEMultipart
+from email.mime.nonmultipart import MIMENonMultipart
+from email.mime.text import MIMEText
+from email import encoders
+
+if sys.version_info[0]==3: unicode=str
+
+if len(sys.argv) != 2:
+ sys.stderr.write("Usage: %s inputfile\n" % sys.argv[0])
+ sys.exit(1)
+
+suffices = {
+ 'wmf':('image','x-wmf'),
+ 'png':('image','png'),
+ 'gif':('image','gif'),
+ 'jpg':('image','jpeg'),
+ 'jpeg':('image','jpeg')
+ }
+
+msg = MIMEMultipart('related',type="text/html")
+# msg['Subject'] = 'Subject here'
+# msg['From'] = '<Saved by ODT2MHT>'
+# msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
+msg.preamble = 'This is a multi-part message in MIME format.'
+msg.epilogue = ''
+odhandler = ODF2XHTML()
+result = odhandler.odf2xhtml(unicode(sys.argv[1]))
+htmlpart = MIMEText(result,'html','us-ascii')
+htmlpart['Content-Location'] = 'index.html'
+msg.attach(htmlpart)
+z = zipfile.ZipFile(sys.argv[1])
+for file in z.namelist():
+ if file[0:9] == 'Pictures/':
+ suffix = file[file.rfind(".")+1:]
+ main,sub = suffices.get(suffix,('application','octet-stream'))
+ img = MIMENonMultipart(main,sub)
+ img.set_payload(z.read(file))
+ img['Content-Location'] = "" + file
+ encoders.encode_base64(img)
+ msg.attach(img)
+z.close()
+print (msg.as_string())
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odf2xhtml b/pip-bins/odf2xhtml
new file mode 100755
index 0000000..d70dfb0
--- /dev/null
+++ b/pip-bins/odf2xhtml
@@ -0,0 +1,59 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+from odf.odf2xhtml import ODF2XHTML
+import sys, getopt
+
+if sys.version_info[0]==3: unicode=str
+
+from io import StringIO
+
+def usage():
+ sys.stderr.write("Usage: %s [-p] inputfile\n" % sys.argv[0])
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "ep", ["plain","embedable"])
+except getopt.GetoptError:
+ usage()
+ sys.exit(2)
+
+generatecss = True
+embedable = False
+for o, a in opts:
+ if o in ("-p", "--plain"):
+ generatecss = False
+ if o in ("-e", "--embedable"):
+ embedable = True
+
+if len(args) != 1:
+ usage()
+ sys.exit(2)
+
+odhandler = ODF2XHTML(generatecss, embedable)
+try:
+ result = odhandler.odf2xhtml(unicode(args[0]))
+except:
+ sys.stderr.write("Unable to open file %s or file is not OpenDocument\n" % args[0])
+ sys.exit(1)
+sys.stdout.write(result)
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odf2xml b/pip-bins/odf2xml
new file mode 100755
index 0000000..f04d9f1
--- /dev/null
+++ b/pip-bins/odf2xml
@@ -0,0 +1,81 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+#
+
+# OpenDocument can be a complete office document in a single
+# XML document. This script will create such a document.
+import sys, getopt, base64
+from odf.opendocument import load
+from odf.draw import Image, ObjectOle
+from odf.style import BackgroundImage
+from odf.text import ListLevelStyleImage
+from odf.office import BinaryData
+
+if sys.version_info[0]==3: unicode=str
+
+def usage():
+ sys.stderr.write("Usage: %s [-e] [-o outputfile] [inputfile]\n" % sys.argv[0])
+
+
+if __name__ == "__main__":
+ embedimage = False
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "o:e", ["output="])
+ except getopt.GetoptError:
+ usage()
+ sys.exit(2)
+
+ outputfile = '-'
+
+ for o, a in opts:
+ if o in ("-o", "--output"):
+ outputfile = a
+ if o == '-e':
+ embedimage = True
+
+ if len(args) > 1:
+ usage()
+ sys.exit(2)
+ if len(args) == 0:
+ d = load(sys.stdin)
+ else:
+ d = load(unicode(args[0]))
+ if embedimage:
+ images = d.getElementsByType(Image) + \
+ d.getElementsByType(BackgroundImage) + \
+ d.getElementsByType(ObjectOle) + \
+ d.getElementsByType(ListLevelStyleImage)
+ for image in images:
+ href = image.getAttribute('href')
+ if href and href[:9] == "Pictures/":
+ p = d.Pictures[href]
+ bp = base64.encodestring(p[1])
+ image.addElement(BinaryData(text=bp))
+ image.removeAttribute('href')
+ xml = d.xml()
+ if outputfile == '-':
+ print (xml)
+ else:
+ open(outputfile,"wb").write(xml)
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odfimgimport b/pip-bins/odfimgimport
new file mode 100755
index 0000000..bd95fed
--- /dev/null
+++ b/pip-bins/odfimgimport
@@ -0,0 +1,190 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2009 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+from __future__ import print_function
+
+import zipfile, sys, getopt, mimetypes
+try:
+ from urllib2 import urlopen, quote, unquote
+except ImportError:
+ from urllib.request import urlopen, quote, unquote
+try:
+ from urlparse import urlunsplit, urlsplit
+except ImportError:
+ from urllib.parse import urlunsplit, urlsplit
+from odf.opendocument import load
+from odf.draw import Image
+
+if sys.version_info[0]==3: unicode=str
+
+#sys.tracebacklimit = 0
+
+# Variable to count the number of retrieval failures
+failures = 0
+
+# Set to one if quiet behaviour is wanted
+quiet = 0
+
+# If set will write every url to import
+verbose = 0
+
+# Dictionary with new pictures. Key is original file path
+# Item is newfilename
+newpictures = {}
+doc = None
+
+def importpicture(href):
+ """ Add the picture to the ZIP file
+ Returns the new path name to the file in the zip archive
+ If it is unable to import, then it returns the original href
+ Sideeffect: add line to manifest
+ """
+ global doc, newpictures, failures, verbose
+
+ # Check that it is not already in the manifest
+ if href in doc.Pictures: return href
+
+ image = None
+ if verbose: print ("Importing", href, file=sys.stderr)
+ if href[:7] == "http://" or href[:8] == "https://" or href[:6] == "ftp://":
+ # There is a bug in urlopen: It can't open urls with non-ascii unicode
+ # characters. Convert to UTF-8 and then use percent encoding
+ try:
+ goodhref = href.encode('ascii')
+ except:
+ o = list(urlsplit(href))
+ o[2] = quote(o[2].encode('utf-8'))
+ goodhref = urlunsplit(o)
+ if goodhref in newpictures:
+ if verbose: print ("already imported", file=sys.stderr)
+ return newpictures[goodhref] # Already imported
+ try:
+ f = urlopen(goodhref.decode("utf-8"))
+ image = f.read()
+ headers = f.info()
+ f.close()
+ # Get the mimetype from the headerlines
+ c_t = headers['Content-Type'].split(';')[0].strip()
+ if c_t: mediatype = c_t.split(';')[0].strip()
+ if verbose: print ("OK", file=sys.stderr)
+ except:
+ failures += 1
+ if verbose: print ("failed", file=sys.stderr)
+ return href
+ # Remove query string
+ try: href= href[:href.rindex('?')]
+ except: pass
+ try:
+ lastslash = href[href.rindex('/'):]
+ ext = lastslash[lastslash.rindex('.'):]
+ except: ext = mimetypes.guess_extension(mediatype)
+ # Everything is a simple path.
+ else:
+ goodhref = href
+ if href[:3] == '../':
+ if directory is None:
+ goodhref = unquote(href[3:])
+ else:
+ goodhref = unquote(directory + href[2:])
+ if goodhref in newpictures:
+ if verbose: print ("already imported", file=sys.stderr)
+ return newpictures[goodhref] # Already imported
+ mediatype, encoding = mimetypes.guess_type(goodhref)
+ if mediatype is None:
+ mediatype = ''
+ try: ext = goodhref[goodhref.rindex('.'):]
+ except: ext=''
+ else:
+ ext = mimetypes.guess_extension(mediatype)
+ try:
+ image = file(goodhref).read()
+ if verbose: print ("OK", file=sys.stderr)
+ except:
+ failures += 1
+ if verbose: print ("failed", file=sys.stderr)
+ return href
+ # If we have a picture to import, the image variable contains it
+ # and manifestfn, ext and mediatype has a value
+ if image:
+ manifestfn = doc.addPictureFromString(image, unicode(mediatype))
+ newpictures[goodhref] = manifestfn
+ return manifestfn
+
+ if verbose: print ("not imported", file=sys.stderr)
+ return href
+
+def exitwithusage(exitcode=2):
+ """ Print out usage information and exit """
+ print ("Usage: %s [-q] [-v] [-o output] [inputfile]" % sys.argv[0], file=sys.stderr)
+ print ("\tInputfile must be OpenDocument format", file=sys.stderr)
+ sys.exit(exitcode)
+
+outputfile = None
+writefile = True
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "qvo:")
+except getopt.GetoptError:
+ exitwithusage()
+
+for o, a in opts:
+ if o == "-o":
+ outputfile = a
+ writefile = True
+ if o == "-q":
+ quiet = 1
+ if o == "-v":
+ verbose = 1
+
+if len(args) == 0:
+ try:
+ doc = load(sys.stdin)
+ directory = None
+ except:
+ print ("Couldn't open OpenDocument file", file=sys.stderr)
+ exitwithusage()
+else:
+ fn = unicode(args[0])
+ if not zipfile.is_zipfile(fn):
+ exitwithusage()
+ dirinx = max(fn.rfind('\\'), fn.rfind('/'))
+ if dirinx >= 0: directory = fn[:dirinx]
+ else: directory = "."
+ doc = load(fn)
+
+for image in doc.getElementsByType(Image):
+ href = image.getAttribute('href')
+ newhref = importpicture(href)
+ image.setAttribute('href',newhref)
+
+if writefile:
+ if outputfile is None:
+ doc.save(fn)
+ else:
+ doc.save(unicode(outputfile))
+
+
+if quiet == 0 and failures > 0:
+ print ("Couldn't import %d image(s)" % failures, file=sys.stderr)
+sys.exit( int(failures > 0) )
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odflint b/pip-bins/odflint
new file mode 100755
index 0000000..e5da6bb
--- /dev/null
+++ b/pip-bins/odflint
@@ -0,0 +1,216 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2009 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+import zipfile
+from xml.sax import make_parser,handler
+from xml.sax.xmlreader import InputSource
+import xml.sax.saxutils
+import sys
+from odf.opendocument import OpenDocument
+from odf import element, grammar
+from odf.namespaces import *
+from odf.attrconverters import attrconverters, cnv_string
+
+from io import BytesIO
+
+if sys.version_info[0]==3: unicode=str
+
+extension_attributes = {
+ "OpenOffice.org" : {
+ (METANS,u'template'): (
+ (XLINKNS,u'role'),
+ ),
+ (STYLENS,u'graphic-properties'): (
+ (STYLENS,u'background-transparency'),
+ ),
+ (STYLENS,u'paragraph-properties'): (
+ (TEXTNS,u'enable-numbering'),
+ (STYLENS,u'join-border'),
+ ),
+ (STYLENS,u'table-cell-properties'): (
+ (STYLENS,u'writing-mode'),
+ ),
+ (STYLENS,u'table-row-properties'): (
+ (STYLENS,u'keep-together'),
+ ),
+ },
+ "KOffice" : {
+ (STYLENS,u'graphic-properties'): (
+ (KOFFICENS,u'frame-behavior-on-new-page'),
+ ),
+ (DRAWNS,u'page'): (
+ (KOFFICENS,u'name'),
+ ),
+ (PRESENTATIONNS,u'show-shape'): (
+ (KOFFICENS,u'order-id'),
+ ),
+ (PRESENTATIONNS,u'hide-shape'): (
+ (KOFFICENS,u'order-id'),
+ ),
+ (CHARTNS,u'legend'): (
+ (KOFFICENS,u'title'),
+ ),
+ }
+}
+
+printed_errors = []
+
+def print_error(str):
+ if str not in printed_errors:
+ printed_errors.append(str)
+ print (str)
+
+def chop_arg(arg):
+ if len(arg) > 20:
+ return "%s..." % arg[0:20]
+ return arg
+
+def make_qname(tag):
+ return "%s:%s" % (nsdict.get(tag[0],tag[0]), tag[1])
+
+def allowed_attributes(tag):
+ return grammar.allowed_attributes.get(tag)
+
+
+class ODFElementHandler(handler.ContentHandler):
+ """ Extract headings from content.xml of an ODT file """
+ def __init__(self, document):
+ self.doc = document
+ self.tagstack = []
+ self.data = []
+ self.currtag = None
+
+ def characters(self, data):
+ self.data.append(data)
+
+ def startElementNS(self, tag, qname, attrs):
+ """ Pseudo-create an element
+ """
+ allowed_attrs = grammar.allowed_attributes.get(tag)
+ attrdict = {}
+ for (att,value) in attrs.items():
+ prefix = nsdict.get(att[0],att[0])
+ # Check if it is a known extension
+ notan_extension = True
+ for product, ext_attrs in extension_attributes.items():
+ allowed_ext_attrs = ext_attrs.get(tag)
+ if allowed_ext_attrs and att in allowed_ext_attrs:
+ print_error("Warning: Attribute %s in element <%s> is illegal - %s extension" % ( make_qname(att), make_qname(tag), product))
+ notan_extension = False
+ # Check if it is an allowed attribute
+ if notan_extension and allowed_attrs and att not in allowed_attrs:
+ print_error("Error: Attribute %s:%s is not allowed in element <%s>" % ( prefix, att[1], make_qname(tag)))
+ # Check the value
+ try:
+ convert = attrconverters.get(att, cnv_string)
+ convert(att, value, tag)
+ except ValueError as res:
+ print_error("Error: Bad value '%s' for attribute %s:%s in tag: <%s> - %s" %
+ (chop_arg(value), prefix, att[1], make_qname(tag), res))
+
+ self.tagstack.append(tag)
+ self.data = []
+ # Check that the parent allows this child element
+ if tag not in ( (OFFICENS, 'document'), (OFFICENS, 'document-content'), (OFFICENS, 'document-styles'),
+ (OFFICENS, 'document-meta'), (OFFICENS, 'document-settings'),
+ (MANIFESTNS,'manifest')):
+ try:
+ parent = self.tagstack[-2]
+ allowed_children = grammar.allowed_children.get(parent)
+ except:
+ print_error("Error: This document starts with the wrong tag: <%s>" % make_qname(tag))
+ allowed_children = None
+ if allowed_children and tag not in allowed_children:
+ print_error("Error: Element %s is not allowed in element %s" % ( make_qname(tag), make_qname(parent)))
+ # Test that all mandatory attributes have been added.
+ required = grammar.required_attributes.get(tag)
+ if required:
+ for r in required:
+ if attrs.get(r) is None:
+ print_error("Error: Required attribute missing: %s in <%s>" % (make_qname(r), make_qname(tag)))
+
+
+ def endElementNS(self, tag, qname):
+ self.currtag = self.tagstack.pop()
+ str = ''.join(self.data).strip()
+ # Check that only elements that can take text have text
+ # But only elements we know exist in grammar
+ if tag in grammar.allowed_children:
+ if str != '' and tag not in grammar.allows_text:
+ print_error("Error: %s does not allow text data" % make_qname(tag))
+ self.data = []
+
+class ODFDTDHandler(handler.DTDHandler):
+ def notationDecl(self, name, public_id, system_id):
+ """ Ignore DTDs """
+ print_error("Warning: ODF doesn't use DOCTYPEs")
+
+def exitwithusage(exitcode=2):
+ """ print out usage information """
+ sys.stderr.write("Usage: %s inputfile\n" % sys.argv[0])
+ sys.stderr.write("\tInputfile must be OpenDocument format\n")
+ sys.exit(exitcode)
+
+def lint(odffile):
+ if not zipfile.is_zipfile(odffile):
+ print_error("Error: This is not a zipped file")
+ return
+ zfd = zipfile.ZipFile(odffile)
+ try:
+ mimetype = zfd.read('mimetype')
+ except:
+ mimetype=''
+ d = OpenDocument(unicode(mimetype))
+ first = True
+ for zi in zfd.infolist():
+ if first:
+ if zi.filename == 'mimetype':
+ if zi.compress_type != zipfile.ZIP_STORED:
+ print_error("Error: The 'mimetype' member must be stored - not deflated")
+ if zi.comment != "":
+ print_error("Error: The 'mimetype' member must not have extra header info")
+ else:
+ print_error("Warning: The first member in the archive should be the mimetype")
+ first = False
+ if zi.filename in ('META-INF/manifest.xml', 'content.xml', 'meta.xml', 'styles.xml', 'settings.xml'):
+ content = zfd.read(zi.filename)
+ parser = make_parser()
+ parser.setFeature(handler.feature_namespaces, True)
+ parser.setFeature(handler.feature_external_ges, False)
+ parser.setContentHandler(ODFElementHandler(d))
+ dtdh = ODFDTDHandler()
+ parser.setDTDHandler(dtdh)
+ parser.setErrorHandler(handler.ErrorHandler())
+
+ inpsrc = InputSource()
+ if not isinstance(content, str):
+ content=content
+ inpsrc.setByteStream(BytesIO(content))
+ parser.parse(inpsrc)
+
+
+if len(sys.argv) != 2:
+ exitwithusage()
+lint(unicode(sys.argv[1]))
+
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odfmeta b/pip-bins/odfmeta
new file mode 100755
index 0000000..b99be94
--- /dev/null
+++ b/pip-bins/odfmeta
@@ -0,0 +1,266 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006-2009 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+import zipfile, time, sys, getopt, re
+import xml.sax, xml.sax.saxutils
+from odf.namespaces import TOOLSVERSION, OFFICENS, XLINKNS, DCNS, METANS
+from io import BytesIO
+
+OUTENCODING="utf-8"
+
+whitespace = re.compile(r'\s+')
+
+fields = {
+'title': (DCNS,u'title'),
+'description': (DCNS,u'description'),
+'subject': (DCNS,u'subject'),
+'creator': (DCNS,u'creator'),
+'date': (DCNS,u'date'),
+'language': (DCNS,u'language'),
+'generator': (METANS,u'generator'),
+'initial-creator': (METANS,u'initial-creator'),
+'keyword': (METANS,u'keyword'),
+'editing-duration': (METANS,u'editing-duration'),
+'editing-cycles': (METANS,u'editing-cycles'),
+'printed-by': (METANS,u'printed-by'),
+'print-date': (METANS,u'print-date'),
+'creation-date': (METANS,u'creation-date'),
+'user-defined': (METANS,u'user-defined'),
+#'template': (METANS,u'template'),
+}
+
+xfields = []
+Xfields = []
+addfields = {}
+deletefields = {}
+yieldfields = {}
+showversion = None
+
+def exitwithusage(exitcode=2):
+ """ print out usage information """
+ sys.stderr.write("Usage: %s [-cdlvV] [-xXaAI metafield]... [-o output] [inputfile]\n" % sys.argv[0])
+ sys.stderr.write("\tInputfile must be OpenDocument format\n")
+ sys.exit(exitcode)
+
+def normalize(str):
+ """
+ The normalize-space function returns the argument string with whitespace
+ normalized by stripping leading and trailing whitespace and replacing
+ sequences of whitespace characters by a single space.
+ """
+ return whitespace.sub(' ', str).strip()
+
+class MetaCollector:
+ """
+ The MetaCollector is a pseudo file object, that can temporarily ignore write-calls
+ It could probably be replaced with a StringIO object.
+ """
+ def __init__(self):
+ self._content = []
+ self.dowrite = True
+
+ def write(self, str):
+ if self.dowrite:
+ self._content.append(str)
+
+ def content(self):
+ return ''.join(self._content)
+
+
+base = xml.sax.saxutils.XMLGenerator
+
+class odfmetaparser(base):
+ """ Parse a meta.xml file with an event-driven parser and replace elements.
+ It would probably be a cleaner approach to use a DOM based parser and
+ then manipulate in memory.
+ Small issue: Reorders elements
+ """
+ version = 'Unknown'
+
+ def __init__(self):
+ self._mimetype = ''
+ self.output = MetaCollector()
+ self._data = []
+ self.seenfields = {}
+ base.__init__(self, self.output, OUTENCODING)
+
+ def startElementNS(self, name, qname, attrs):
+ self._data = []
+ field = name
+# I can't modify the template until the tool replaces elements at the same
+# location and not at the end
+# if name == (METANS,u'template'):
+# self._data = [attrs.get((XLINKNS,u'title'),'')]
+ if showversion and name == (OFFICENS,u'document-meta'):
+ if showversion == '-V':
+ print ("version:%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8'))
+ else:
+ print ("%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8'))
+ if name == (METANS,u'user-defined'):
+ field = attrs.get((METANS,u'name'))
+ if field in deletefields:
+ self.output.dowrite = False
+ elif field in yieldfields:
+ del addfields[field]
+ base.startElementNS(self, name, qname, attrs)
+ else:
+ base.startElementNS(self, name, qname, attrs)
+ self._tag = field
+
+ def endElementNS(self, name, qname):
+ field = name
+ if name == (METANS,u'user-defined'):
+ field = self._tag
+ if name == (OFFICENS,u'meta'):
+ for k,v in addfields.items():
+ if len(v) > 0:
+ if type(k) == type(''):
+ base.startElementNS(self,(METANS,u'user-defined'),None,{(METANS,u'name'):k})
+ base.characters(self, v)
+ base.endElementNS(self, (METANS,u'user-defined'),None)
+ else:
+ base.startElementNS(self, k, None, {})
+ base.characters(self, v)
+ base.endElementNS(self, k, None)
+ if name in xfields:
+ print ("%s" % self.data())
+ if name in Xfields:
+ if isinstance(self._tag, tuple):
+ texttag = self._tag[1]
+ else:
+ texttag = self._tag
+ print ("%s:%s" % (texttag, self.data()))
+ if field in deletefields:
+ self.output.dowrite = True
+ else:
+ base.endElementNS(self, name, qname)
+
+ def characters(self, content):
+ base.characters(self, content)
+ self._data.append(content)
+
+ def meta(self):
+ return self.output.content()
+
+ def data(self):
+ if usenormalize:
+ return normalize(''.join(self._data))
+ else:
+ return ''.join(self._data)
+
+now = time.localtime()[:6]
+outputfile = "-"
+writemeta = False # Do we change any meta data?
+usenormalize = False
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "cdlvVI:A:a:o:x:X:")
+except getopt.GetoptError:
+ exitwithusage()
+
+if len(opts) == 0:
+ opts = [ ('-l','') ]
+
+for o, a in opts:
+ if o in ('-a','-A','-I'):
+ writemeta = True
+ if a.find(":") >= 0:
+ k,v = a.split(":",1)
+ else:
+ k,v = (a, "")
+ if len(k) == 0:
+ exitwithusage()
+ k = fields.get(k,k)
+ addfields[k] = unicode(v,'utf-8')
+ if o == '-a':
+ yieldfields[k] = True
+ if o == '-I':
+ deletefields[k] = True
+ if o == '-d':
+ writemeta = True
+ addfields[(DCNS,u'date')] = "%04d-%02d-%02dT%02d:%02d:%02d" % now
+ deletefields[(DCNS,u'date')] = True
+ if o == '-c':
+ usenormalize = True
+ if o in ('-v', '-V'):
+ showversion = o
+ if o == '-l':
+ Xfields = fields.values()
+ if o == "-x":
+ xfields.append(fields.get(a,a))
+ if o == "-X":
+ Xfields.append(fields.get(a,a))
+ if o == "-o":
+ outputfile = a
+
+# The specification says we should change the element to our own,
+# and must not export the original identifier.
+if writemeta:
+ addfields[(METANS,u'generator')] = TOOLSVERSION
+ deletefields[(METANS,u'generator')] = True
+
+odfs = odfmetaparser()
+parser = xml.sax.make_parser()
+parser.setFeature(xml.sax.handler.feature_namespaces, 1)
+parser.setContentHandler(odfs)
+
+if len(args) == 0:
+ zin = zipfile.ZipFile(sys.stdin,'r')
+else:
+ if not zipfile.is_zipfile(args[0]):
+ exitwithusage()
+ zin = zipfile.ZipFile(args[0], 'r')
+
+try:
+ content = zin.read('meta.xml').decode('utf-8')
+except:
+ sys.stderr.write("File has no meta data\n")
+ sys.exit(1)
+parser.parse(BytesIO(content.encode('utf-8')))
+
+if writemeta:
+ if outputfile == '-':
+ if sys.stdout.isatty():
+ sys.stderr.write("Won't write ODF file to terminal\n")
+ sys.exit(1)
+ zout = zipfile.ZipFile(sys.stdout,"w")
+ else:
+ zout = zipfile.ZipFile(outputfile,"w")
+
+
+
+ # Loop through the input zipfile and copy the content to the output until we
+ # get to the meta.xml. Then substitute.
+ for zinfo in zin.infolist():
+ if zinfo.filename == "meta.xml":
+ # Write meta
+ zi = zipfile.ZipInfo("meta.xml", now)
+ zi.compress_type = zipfile.ZIP_DEFLATED
+ zout.writestr(zi,odfs.meta() )
+ else:
+ payload = zin.read(zinfo.filename)
+ zout.writestr(zinfo, payload)
+
+ zout.close()
+zin.close()
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odfoutline b/pip-bins/odfoutline
new file mode 100755
index 0000000..0d26071
--- /dev/null
+++ b/pip-bins/odfoutline
@@ -0,0 +1,144 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+from __future__ import print_function
+import zipfile
+from xml.sax import make_parser,handler
+from xml.sax.xmlreader import InputSource
+import xml.sax.saxutils
+import sys
+from odf.namespaces import TEXTNS, TABLENS, DRAWNS
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+
+def getxmlpart(odffile, xmlfile):
+ """ Get the content out of the ODT file"""
+ z = zipfile.ZipFile(odffile)
+ content = z.read(xmlfile)
+ z.close()
+ return content
+
+
+
+#
+# Extract headings from content.xml
+#
+class ODTHeadingHandler(handler.ContentHandler):
+ """ Extract headings from content.xml of an ODT file """
+ def __init__(self, eater):
+ self.r = eater
+ self.data = []
+ self.level = 0
+
+ def characters(self, data):
+ self.data.append(data)
+
+ def startElementNS(self, tag, qname, attrs):
+ if tag == (TEXTNS, 'h'):
+ self.level = 0
+ for (att,value) in attrs.items():
+ if att == (TEXTNS, 'outline-level'):
+ self.level = int(value)
+ self.data = []
+
+ def endElementNS(self, tag, qname):
+ if tag == (TEXTNS, 'h'):
+ str = ''.join(self.data)
+ self.data = []
+ self.r.append("%d%*s%s" % (self.level, self.level, '', str))
+
+class ODTSheetHandler(handler.ContentHandler):
+ """ Extract sheet names from content.xml of an ODS file """
+ def __init__(self, eater):
+ self.r = eater
+
+ def startElementNS(self, tag, qname, attrs):
+ if tag == (TABLENS, 'table'):
+ sheetname = attrs.get((TABLENS, 'name'))
+ if sheetname:
+ self.r.append(sheetname)
+
+class ODTSlideHandler(handler.ContentHandler):
+ """ Extract headings from content.xml of an ODT file """
+ def __init__(self, eater):
+ self.r = eater
+ self.data = []
+ self.pagenum = 0
+
+ def characters(self, data):
+ self.data.append(data)
+
+ def startElementNS(self, tag, qname, attrs):
+ if tag == (DRAWNS, 'page'):
+ self.pagenum = self.pagenum + 1
+ self.r.append("SLIDE %d: %s" % ( self.pagenum, attrs.get((DRAWNS, 'name'),'')))
+ if tag == (TEXTNS, 'p'):
+ self.data = []
+
+ def endElementNS(self, tag, qname):
+ if tag == (TEXTNS, 'p'):
+ str = ''.join(self.data)
+ self.data = []
+ if len(str) > 0:
+ self.r.append(" " + str)
+
+def odtheadings(odtfile):
+ mimetype = getxmlpart(odtfile,'mimetype')
+ content = getxmlpart(odtfile,'content.xml')
+ lines = []
+ parser = make_parser()
+ parser.setFeature(handler.feature_namespaces, 1)
+ if not isinstance(mimetype, str):
+ mimetype=mimetype.decode("utf-8")
+ if mimetype in ('application/vnd.oasis.opendocument.text',
+ 'application/vnd.oasis.opendocument.text-template'):
+ parser.setContentHandler(ODTHeadingHandler(lines))
+ elif mimetype in ('application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.oasis.opendocument.spreadsheet-template'):
+ parser.setContentHandler(ODTSheetHandler(lines))
+ elif mimetype in ('application/vnd.oasis.opendocument.presentation'
+ 'application/vnd.oasis.opendocument.presentation-template'):
+ parser.setContentHandler(ODTSlideHandler(lines))
+ else:
+ print ("Unsupported fileformat")
+ sys.exit(2)
+ parser.setErrorHandler(handler.ErrorHandler())
+
+ inpsrc = InputSource()
+ if not isinstance(content, str):
+ content=content.decode("utf-8")
+ inpsrc.setByteStream(StringIO(content))
+ parser.parse(inpsrc)
+ return lines
+
+
+if __name__ == "__main__":
+ filler = " "
+ for heading in odtheadings(sys.argv[1]):
+ print (heading)
+
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/odfuserfield b/pip-bins/odfuserfield
new file mode 100755
index 0000000..cae1574
--- /dev/null
+++ b/pip-bins/odfuserfield
@@ -0,0 +1,101 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s): Michael Howitz, gocept gmbh & co. kg
+
+import sys
+import getopt
+
+import odf.userfield
+
+if sys.version_info[0]==3: unicode=str
+
+listfields = False
+Listfields = False
+xfields = []
+Xfields = []
+setfields = {}
+outputfile = None
+inputfile = None
+
+
+def exitwithusage(exitcode=2):
+ """ print out usage information """
+ sys.stderr.write("Usage: %s [-lL] [-xX metafield] [-s metafield:value]... "
+ "[-o output] [inputfile]\n" % sys.argv[0])
+ sys.stderr.write("\tInputfile must be OpenDocument format\n")
+ sys.exit(exitcode)
+
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "lLs:o:x:X:")
+except getopt.GetoptError:
+ exitwithusage()
+
+if len(opts) == 0:
+ exitwithusage()
+
+for o, a in opts:
+ if o == '-s':
+ if a.find(":") >= 0:
+ k,v = a.split(":",1)
+ else:
+ k,v = (a, "")
+ if len(k) == 0:
+ exitwithusage()
+ setfields[unicode(k)] = unicode(v)
+ if o == '-l':
+ listfields = True
+ Listfields = False
+ if o == '-L':
+ Listfields = True
+ listfields = False
+ if o == "-x":
+ xfields.append(unicode(a))
+ if o == "-X":
+ Xfields.append(unicode(a))
+ if o == "-o":
+ outputfile = unicode(a)
+
+if len(args) != 0:
+ inputfile = unicode(args[0])
+
+user_fields = odf.userfield.UserFields(inputfile, outputfile)
+
+if xfields:
+ for value in user_fields.list_values(xfields):
+ print (value)
+
+if Listfields or Xfields:
+ if Listfields:
+ Xfields = None
+ for field_name, value_type, value in user_fields.list_fields_and_values(
+ Xfields):
+ print ("%s#%s:%s" % (field_name, value_type, value))
+
+if listfields:
+ for value in user_fields.list_fields():
+ print (value)
+
+if setfields:
+ user_fields.update(setfields)
+
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***
diff --git a/pip-bins/pdf2txt.py b/pip-bins/pdf2txt.py
new file mode 100755
index 0000000..cda5b88
--- /dev/null
+++ b/pip-bins/pdf2txt.py
@@ -0,0 +1,324 @@
+#!/usr/bin/python3
+"""A command line tool for extracting text and images from PDF and
+output it to plain text, html, xml or tags.
+"""
+
+import argparse
+import logging
+import sys
+from collections.abc import Container, Iterable
+from typing import Any
+
+import pdfminer.high_level
+from pdfminer.layout import LAParams
+from pdfminer.pdfexceptions import PDFValueError
+from pdfminer.utils import AnyIO
+
+logging.basicConfig()
+
+OUTPUT_TYPES = ((".htm", "html"), (".html", "html"), (".xml", "xml"), (".tag", "tag"))
+
+
+def float_or_disabled(x: str) -> float | None:
+ if x.lower().strip() == "disabled":
+ return None
+ try:
+ return float(x)
+ except ValueError as err:
+ raise argparse.ArgumentTypeError(f"invalid float value: {x}") from err
+
+
+def extract_text(
+ files: Iterable[str] = [],
+ outfile: str = "-",
+ laparams: LAParams | None = None,
+ output_type: str = "text",
+ codec: str = "utf-8",
+ strip_control: bool = False,
+ maxpages: int = 0,
+ page_numbers: Container[int] | None = None,
+ password: str = "",
+ scale: float = 1.0,
+ rotation: int = 0,
+ layoutmode: str = "normal",
+ output_dir: str | None = None,
+ debug: bool = False,
+ disable_caching: bool = False,
+ **kwargs: Any,
+) -> None:
+ if not files:
+ raise PDFValueError("Must provide files to work upon!")
+
+ if output_type == "text" and outfile != "-":
+ for override, alttype in OUTPUT_TYPES:
+ if outfile.endswith(override):
+ output_type = alttype
+
+ if outfile == "-":
+ outfp: AnyIO = sys.stdout
+ if sys.stdout.encoding is not None:
+ codec = "utf-8"
+ for fname in files:
+ with open(fname, "rb") as fp:
+ pdfminer.high_level.extract_text_to_fp(fp, **locals())
+ else:
+ # Use context manager for file output, ensuring proper cleanup
+ with open(outfile, "wb") as outfp:
+ for fname in files:
+ with open(fname, "rb") as fp:
+ pdfminer.high_level.extract_text_to_fp(fp, **locals())
+
+
+def create_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(description=__doc__, add_help=True)
+ parser.add_argument(
+ "files",
+ type=str,
+ default=None,
+ nargs="+",
+ help="One or more paths to PDF files.",
+ )
+
+ parser.add_argument(
+ "--version",
+ "-v",
+ action="version",
+ version=f"pdfminer.six v{pdfminer.__version__}",
+ )
+ parser.add_argument(
+ "--debug",
+ "-d",
+ default=False,
+ action="store_true",
+ help="Use debug logging level.",
+ )
+ parser.add_argument(
+ "--disable-caching",
+ "-C",
+ default=False,
+ action="store_true",
+ help="If caching or resources, such as fonts, should be disabled.",
+ )
+
+ parse_params = parser.add_argument_group(
+ "Parser",
+ description="Used during PDF parsing",
+ )
+ parse_params.add_argument(
+ "--page-numbers",
+ type=int,
+ default=None,
+ nargs="+",
+ help="A space-seperated list of page numbers to parse.",
+ )
+ parse_params.add_argument(
+ "--pagenos",
+ "-p",
+ type=str,
+ help="A comma-separated list of page numbers to parse. "
+ "Included for legacy applications, use --page-numbers "
+ "for more idiomatic argument entry.",
+ )
+ parse_params.add_argument(
+ "--maxpages",
+ "-m",
+ type=int,
+ default=0,
+ help="The maximum number of pages to parse.",
+ )
+ parse_params.add_argument(
+ "--password",
+ "-P",
+ type=str,
+ default="",
+ help="The password to use for decrypting PDF file.",
+ )
+ parse_params.add_argument(
+ "--rotation",
+ "-R",
+ default=0,
+ type=int,
+ help="The number of degrees to rotate the PDF "
+ "before other types of processing.",
+ )
+
+ la_params = LAParams() # will be used for defaults
+ la_param_group = parser.add_argument_group(
+ "Layout analysis",
+ description="Used during layout analysis.",
+ )
+ la_param_group.add_argument(
+ "--no-laparams",
+ "-n",
+ default=False,
+ action="store_true",
+ help="If layout analysis parameters should be ignored.",
+ )
+ la_param_group.add_argument(
+ "--detect-vertical",
+ "-V",
+ default=la_params.detect_vertical,
+ action="store_true",
+ help="If vertical text should be considered during layout analysis",
+ )
+ la_param_group.add_argument(
+ "--line-overlap",
+ type=float,
+ default=la_params.line_overlap,
+ help="If two characters have more overlap than this they "
+ "are considered to be on the same line. The overlap is specified "
+ "relative to the minimum height of both characters.",
+ )
+ la_param_group.add_argument(
+ "--char-margin",
+ "-M",
+ type=float,
+ default=la_params.char_margin,
+ help="If two characters are closer together than this margin they "
+ "are considered to be part of the same line. The margin is "
+ "specified relative to the width of the character.",
+ )
+ la_param_group.add_argument(
+ "--word-margin",
+ "-W",
+ type=float,
+ default=la_params.word_margin,
+ help="If two characters on the same line are further apart than this "
+ "margin then they are considered to be two separate words, and "
+ "an intermediate space will be added for readability. The margin "
+ "is specified relative to the width of the character.",
+ )
+ la_param_group.add_argument(
+ "--line-margin",
+ "-L",
+ type=float,
+ default=la_params.line_margin,
+ help="If two lines are close together they are considered to "
+ "be part of the same paragraph. The margin is specified "
+ "relative to the height of a line.",
+ )
+ la_param_group.add_argument(
+ "--boxes-flow",
+ "-F",
+ type=float_or_disabled,
+ default=la_params.boxes_flow,
+ help="Specifies how much a horizontal and vertical position of a "
+ "text matters when determining the order of lines. The value "
+ "should be within the range of -1.0 (only horizontal position "
+ "matters) to +1.0 (only vertical position matters). You can also "
+ "pass `disabled` to disable advanced layout analysis, and "
+ "instead return text based on the position of the bottom left "
+ "corner of the text box.",
+ )
+ la_param_group.add_argument(
+ "--all-texts",
+ "-A",
+ default=la_params.all_texts,
+ action="store_true",
+ help="If layout analysis should be performed on text in figures.",
+ )
+
+ output_params = parser.add_argument_group(
+ "Output",
+ description="Used during output generation.",
+ )
+ output_params.add_argument(
+ "--outfile",
+ "-o",
+ type=str,
+ default="-",
+ help="Path to file where output is written. "
+ 'Or "-" (default) to write to stdout.',
+ )
+ output_params.add_argument(
+ "--output_type",
+ "-t",
+ type=str,
+ default="text",
+ help="Type of output to generate {text,html,xml,tag}.",
+ )
+ output_params.add_argument(
+ "--codec",
+ "-c",
+ type=str,
+ default="utf-8",
+ help="Text encoding to use in output file.",
+ )
+ output_params.add_argument(
+ "--output-dir",
+ "-O",
+ default=None,
+ help="The output directory to put extracted images in. If not given, "
+ "images are not extracted.",
+ )
+ output_params.add_argument(
+ "--layoutmode",
+ "-Y",
+ default="normal",
+ type=str,
+ help="Type of layout to use when generating html "
+ "{normal,exact,loose}. If normal,each line is"
+ " positioned separately in the html. If exact"
+ ", each character is positioned separately in"
+ " the html. If loose, same result as normal "
+ "but with an additional newline after each "
+ "text line. Only used when output_type is html.",
+ )
+ output_params.add_argument(
+ "--scale",
+ "-s",
+ type=float,
+ default=1.0,
+ help="The amount of zoom to use when generating html file. "
+ "Only used when output_type is html.",
+ )
+ output_params.add_argument(
+ "--strip-control",
+ "-S",
+ default=False,
+ action="store_true",
+ help="Remove control statement from text. Only used when output_type is xml.",
+ )
+
+ return parser
+
+
+def parse_args(args: list[str] | None) -> argparse.Namespace:
+ parsed_args = create_parser().parse_args(args=args)
+
+ # Propagate parsed layout parameters to LAParams object
+ if parsed_args.no_laparams:
+ parsed_args.laparams = None
+ else:
+ parsed_args.laparams = LAParams(
+ line_overlap=parsed_args.line_overlap,
+ char_margin=parsed_args.char_margin,
+ line_margin=parsed_args.line_margin,
+ word_margin=parsed_args.word_margin,
+ boxes_flow=parsed_args.boxes_flow,
+ detect_vertical=parsed_args.detect_vertical,
+ all_texts=parsed_args.all_texts,
+ )
+
+ if parsed_args.page_numbers:
+ parsed_args.page_numbers = {x - 1 for x in parsed_args.page_numbers}
+
+ if parsed_args.pagenos:
+ parsed_args.page_numbers = {int(x) - 1 for x in parsed_args.pagenos.split(",")}
+
+ if parsed_args.output_type == "text" and parsed_args.outfile != "-":
+ for override, alttype in OUTPUT_TYPES:
+ if parsed_args.outfile.endswith(override):
+ parsed_args.output_type = alttype
+
+ return parsed_args
+
+
+def main(args: list[str] | None = None) -> int:
+ parsed_args = parse_args(args)
+ extract_text(**vars(parsed_args))
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/pip-bins/pdfplumber b/pip-bins/pdfplumber
new file mode 100755
index 0000000..cfb94ab
--- /dev/null
+++ b/pip-bins/pdfplumber
@@ -0,0 +1,8 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pdfplumber.cli import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/pip-bins/pypdfium2 b/pip-bins/pypdfium2
new file mode 100755
index 0000000..ec81a5f
--- /dev/null
+++ b/pip-bins/pypdfium2
@@ -0,0 +1,8 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pypdfium2.__main__ import cli_main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(cli_main())
diff --git a/pip-bins/xml2odf b/pip-bins/xml2odf
new file mode 100755
index 0000000..e7114a8
--- /dev/null
+++ b/pip-bins/xml2odf
@@ -0,0 +1,241 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006 Søren Roug, European Environment Agency
+#
+# This is free software. You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Contributor(s):
+#
+#
+
+# OpenDocument can be a complete office document in a single
+# XML document. This script will take such a document and create
+# a package
+import io
+import zipfile,time, sys, getopt
+import xml.sax, xml.sax.saxutils
+from odf import manifest
+
+class SplitWriter:
+ def __init__(self):
+ self.activefiles = []
+ self._content = []
+ self._meta = []
+ self._styles = []
+ self._settings = []
+
+ self.files = {'content': self._content, 'meta': self._meta,
+ 'styles':self._styles, 'settings': self._settings }
+
+ def write(self, str):
+ for f in self.activefiles:
+ f.append(str)
+
+ def activate(self, filename):
+ file = self.files[filename]
+ if file not in self.activefiles:
+ self.activefiles.append(file)
+
+ def deactivate(self, filename):
+ file = self.files[filename]
+ if file in self.activefiles:
+ self.activefiles.remove(file)
+
+odmimetypes = {
+ 'application/vnd.oasis.opendocument.text': '.odt',
+ 'application/vnd.oasis.opendocument.text-template': '.ott',
+ 'application/vnd.oasis.opendocument.graphics': '.odg',
+ 'application/vnd.oasis.opendocument.graphics-template': '.otg',
+ 'application/vnd.oasis.opendocument.presentation': '.odp',
+ 'application/vnd.oasis.opendocument.presentation-template': '.otp',
+ 'application/vnd.oasis.opendocument.spreadsheet': '.ods',
+ 'application/vnd.oasis.opendocument.spreadsheet-template': '.ots',
+ 'application/vnd.oasis.opendocument.chart': '.odc',
+ 'application/vnd.oasis.opendocument.chart-template': '.otc',
+ 'application/vnd.oasis.opendocument.image': '.odi',
+ 'application/vnd.oasis.opendocument.image-template': '.oti',
+ 'application/vnd.oasis.opendocument.formula': '.odf',
+ 'application/vnd.oasis.opendocument.formula-template': '.otf',
+ 'application/vnd.oasis.opendocument.text-master': '.odm',
+ 'application/vnd.oasis.opendocument.text-web': '.oth',
+}
+
+OFFICENS = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"
+base = xml.sax.saxutils.XMLGenerator
+
+class odfsplitter(base):
+
+ def __init__(self):
+ self._mimetype = ''
+ self.output = SplitWriter()
+ self._prefixes = []
+ base.__init__(self, self.output, 'utf-8')
+
+ def startPrefixMapping(self, prefix, uri):
+ base.startPrefixMapping(self, prefix, uri)
+ self._prefixes.append('xmlns:%s="%s"' % (prefix, uri))
+
+ def startElementNS(self, name, qname, attrs):
+ if name == (OFFICENS, u"document"):
+ self._mimetype = attrs.get((OFFICENS, "mimetype"))
+ elif name == (OFFICENS, u"meta"):
+ self.output.activate('meta')
+
+ elif name == (OFFICENS, u"settings"):
+ self.output.activate('settings')
+ elif name == (OFFICENS, u"scripts"):
+ self.output.activate('content')
+ elif name == (OFFICENS, u"font-face-decls"):
+ self.output.activate('content')
+ self.output.activate('styles')
+ elif name == (OFFICENS, u"styles"):
+ self.output.activate('styles')
+ elif name == (OFFICENS, u"automatic-styles"):
+ self.output.activate('content')
+ self.output.activate('styles')
+ elif name == (OFFICENS, u"master-styles"):
+ self.output.activate('styles')
+ elif name == (OFFICENS, u"body"):
+ self.output.activate('content')
+ base.startElementNS(self, name, qname, attrs)
+
+ def endElementNS(self, name, qname):
+ base.endElementNS(self, name, qname)
+ if name == (OFFICENS, u"meta"):
+ self.output.deactivate('meta')
+ elif name == (OFFICENS, u"settings"):
+ self.output.deactivate('settings')
+ elif name == (OFFICENS, u"scripts"):
+ self.output.deactivate('content')
+ elif name == (OFFICENS, u"font-face-decls"):
+ self.output.deactivate('content')
+ self.output.deactivate('styles')
+ elif name == (OFFICENS, u"styles"):
+ self.output.deactivate('styles')
+ elif name == (OFFICENS, u"automatic-styles"):
+ self.output.deactivate('content')
+ self.output.deactivate('styles')
+ elif name == (OFFICENS, u"master-styles"):
+ self.output.deactivate('styles')
+ elif name == (OFFICENS, u"body"):
+ self.output.deactivate('content')
+
+
+ def content(self):
+ """ Return the content inside a wrapper called <office:document-content>
+ """
+ prefixes = ' '.join(self._prefixes)
+ return ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-content %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._content)) + ['</office:document-content>'])
+
+ def settings(self):
+ prefixes = ' '.join(self._prefixes).encode('utf-8')
+ return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-settings %s office:version="1.0">' % prefixes] + self.output._settings + ['''</office:document-settings>'''])
+
+ def styles(self):
+ prefixes = ' '.join(self._prefixes)
+ return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-styles %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._styles)) + ['''</office:document-styles>'''])
+
+ def meta(self):
+ prefixes = ' '.join(self._prefixes)
+ return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-meta %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._meta)) + ['''</office:document-meta>'''])
+
+def usage():
+ sys.stderr.write("Usage: %s [-o outputfile] [-s] inputfile\n" % sys.argv[0])
+
+def manifestxml(m):
+ """ Generates the content of the manifest.xml file """
+ xml=io.StringIO()
+ xml.write(u"<?xml version='1.0' encoding='UTF-8'?>\n")
+ m.toXml(0,xml)
+ return xml.getvalue()
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "o:s", ["output=","suffix"])
+except getopt.GetoptError:
+ usage()
+ sys.exit(2)
+
+outputfile = '-'
+addsuffix = False
+
+for o, a in opts:
+ if o in ("-o", "--output"):
+ outputfile = a
+ if o in ("-s", "--suffix"):
+ addsuffix = True
+
+if len(args) > 1:
+ usage()
+ sys.exit(2)
+
+odfs = odfsplitter()
+parser = xml.sax.make_parser()
+parser.setFeature(xml.sax.handler.feature_namespaces, 1)
+parser.setContentHandler(odfs)
+if len(args) == 0:
+ parser.parse(sys.stdin)
+else:
+ parser.parse(open(args[0],"r"))
+
+mimetype = odfs._mimetype
+suffix = odmimetypes.get(mimetype,'.xxx')
+
+if outputfile == '-':
+ if sys.stdout.isatty():
+ sys.stderr.write("Won't write ODF file to terminal\n")
+ sys.exit(1)
+ z = zipfile.ZipFile(sys.stdout,"w")
+else:
+ if addsuffix:
+ outputfile = outputfile + suffix
+ z = zipfile.ZipFile(outputfile,"w")
+
+now = time.localtime()[:6]
+
+# Write mimetype
+zi = zipfile.ZipInfo('mimetype', now)
+zi.compress_type = zipfile.ZIP_STORED
+z.writestr(zi,mimetype)
+
+# Write content
+zi = zipfile.ZipInfo("content.xml", now)
+zi.compress_type = zipfile.ZIP_DEFLATED
+z.writestr(zi,odfs.content() )
+# Write styles
+zi = zipfile.ZipInfo("styles.xml", now)
+zi.compress_type = zipfile.ZIP_DEFLATED
+z.writestr(zi,odfs.styles() )
+
+# Write meta
+zi = zipfile.ZipInfo("meta.xml", now)
+zi.compress_type = zipfile.ZIP_DEFLATED
+z.writestr(zi,odfs.meta() )
+
+m = manifest.Manifest()
+m.addElement(manifest.FileEntry(fullpath="/", mediatype=mimetype))
+m.addElement(manifest.FileEntry(fullpath="content.xml",mediatype="text/xml"))
+m.addElement(manifest.FileEntry(fullpath="styles.xml", mediatype="text/xml"))
+m.addElement(manifest.FileEntry(fullpath="meta.xml", mediatype="text/xml"))
+
+# Write manifest
+zi = zipfile.ZipInfo("META-INF/manifest.xml", now)
+zi.compress_type = zipfile.ZIP_DEFLATED
+z.writestr(zi, manifestxml(m).encode("utf-8") )
+z.close()
+
+
+
+# Local Variables: ***
+# mode: python ***
+# End: ***