diff options
| author | Lukasz Kasprzak <lukasz.kasprzak@pm.me> | 2026-03-19 15:05:32 +0100 |
|---|---|---|
| committer | Lukasz Kasprzak <lukasz.kasprzak@pm.me> | 2026-03-19 15:05:32 +0100 |
| commit | b1844d805a8f190af00faf3f6e5bed7d997ecaae (patch) | |
| tree | d6296ba5e85034836e203a542d2ad34a9e5c5b32 /pip-bins | |
| parent | c74163f1ecbd809c83bb465ee89fa04369a89972 (diff) | |
| download | bin-b1844d805a8f190af00faf3f6e5bed7d997ecaae.tar.gz bin-b1844d805a8f190af00faf3f6e5bed7d997ecaae.zip | |
created sorter for sorting downloads with custom rules; created straper for recreation of server
Diffstat (limited to 'pip-bins')
| -rwxr-xr-x | pip-bins/csv2ods | 229 | ||||
| -rwxr-xr-x | pip-bins/dumppdf.py | 468 | ||||
| -rwxr-xr-x | pip-bins/odf2mht | 72 | ||||
| -rwxr-xr-x | pip-bins/odf2xhtml | 59 | ||||
| -rwxr-xr-x | pip-bins/odf2xml | 81 | ||||
| -rwxr-xr-x | pip-bins/odfimgimport | 190 | ||||
| -rwxr-xr-x | pip-bins/odflint | 216 | ||||
| -rwxr-xr-x | pip-bins/odfmeta | 266 | ||||
| -rwxr-xr-x | pip-bins/odfoutline | 144 | ||||
| -rwxr-xr-x | pip-bins/odfuserfield | 101 | ||||
| -rwxr-xr-x | pip-bins/pdf2txt.py | 324 | ||||
| -rwxr-xr-x | pip-bins/pdfplumber | 8 | ||||
| -rwxr-xr-x | pip-bins/pypdfium2 | 8 | ||||
| -rwxr-xr-x | pip-bins/xml2odf | 241 |
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: *** |
