Add support for the Open Document Format (ODT) as an input format. Can now be converted to both LRF and EPUB. Fixes #233 (ODT2LRF request)

This commit is contained in:
Kovid Goyal 2008-10-06 18:11:39 -07:00
parent c1995474be
commit 323dfce595
46 changed files with 16711 additions and 7 deletions

View File

@ -28,3 +28,4 @@ src/cssutils/_todo/
src/cssutils/scripts/
src/cssutils/css/.svn/
src/cssutils/stylesheets/.svn/
src/odf/.svn

View File

@ -21,4 +21,4 @@ class DRMError(ValueError):
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
'html', 'xhtml', 'epub', 'pdf', 'prc', 'mobi', 'azw',
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'oebzip',
'rb', 'imp']
'rb', 'imp', 'odt']

View File

@ -72,7 +72,6 @@ def pdf2opf(path, tdir, opts):
def epub2opf(path, tdir, opts):
zf = ZipFile(path)
zf.extractall(tdir)
print os.listdir(os.path.join(tdir, 'META-INF'))
if os.path.exists(os.path.join(tdir, 'META-INF', 'encryption.xml')):
raise DRMError(os.path.basename(path))
for f in walk(tdir):
@ -80,6 +79,10 @@ def epub2opf(path, tdir, opts):
return f
raise ValueError('%s is not a valid EPUB file'%path)
def odt2epub(path, tdir, opts):
from calibre.ebooks.odt.to_oeb import Extract
opts.encoding = 'utf-8'
return Extract()(path, tdir)
MAP = {
'lit' : lit2opf,
@ -90,8 +93,9 @@ MAP = {
'txt' : txt2opf,
'pdf' : pdf2opf,
'epub' : epub2opf,
'odt' : odt2epub,
}
SOURCE_FORMATS = ['lit', 'mobi', 'prc', 'fb2', 'rtf', 'txt', 'pdf', 'rar', 'zip', 'oebzip', 'htm', 'html', 'epub']
SOURCE_FORMATS = ['lit', 'mobi', 'prc', 'fb2', 'odt', 'rtf', 'txt', 'pdf', 'rar', 'zip', 'oebzip', 'htm', 'html', 'epub']
def unarchive(path, tdir):
extract(path, tdir)

View File

@ -23,6 +23,7 @@ preferred_source_formats = [
'LIT',
'MOBI',
'EPUB',
'ODT',
'HTML',
'HTM',
'XHTM',

View File

@ -1,3 +1,4 @@
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Convert any ebook file into a LRF file.'''
@ -7,6 +8,7 @@ import sys, os, logging, shutil, tempfile, re
from calibre.ebooks import UnknownFormatError
from calibre.ebooks.lrf import option_parser as _option_parser
from calibre import __appname__, setup_cli_handlers, extract
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.lrf.lit.convert_from import process_file as lit2lrf
from calibre.ebooks.lrf.pdf.convert_from import process_file as pdf2lrf
from calibre.ebooks.lrf.rtf.convert_from import process_file as rtf2lrf
@ -89,6 +91,21 @@ def handle_archive(path):
file = file.decode(sys.getfilesystemencoding())
return tdir, file
def odt2lrf(path, options, logger):
from calibre.ebooks.odt.to_oeb import Extract
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
if logger is None:
level = logging.DEBUG if options.verbose else logging.INFO
logger = logging.getLogger('odt2lrf')
setup_cli_handlers(logger, level)
with TemporaryDirectory('_odt2lrf') as tdir:
opf = Extract()(path, tdir)
options.use_spine = True
options.encoding = 'utf-8'
html_process_file(opf.replace('metadata.opf', 'index.html'), options, logger)
def process_file(path, options, logger=None):
path = os.path.abspath(os.path.expanduser(path))
tdir = None
@ -138,8 +155,10 @@ def process_file(path, options, logger=None):
convertor = mobi2lrf
elif ext == 'fb2':
convertor = fb22lrf
elif ext == 'odt':
convertor = odt2lrf
if not convertor:
raise UnknownFormatError('Coverting from %s to LRF is not supported.'%ext)
raise UnknownFormatError(_('Converting from %s to LRF is not supported.')%ext)
convertor(path, options, logger)
finally:
os.chdir(cwd)

View File

@ -14,17 +14,17 @@ from calibre.ebooks.metadata.rb import get_metadata as rb_metadata
from calibre.ebooks.metadata.epub import get_metadata as epub_metadata
from calibre.ebooks.metadata.html import get_metadata as html_metadata
from calibre.ebooks.mobi.reader import get_metadata as mobi_metadata
from calibre.ebooks.metadata.odt import get_metadata as odt_metadata
from calibre.ebooks.metadata.opf import OPFReader
from calibre.ebooks.metadata.rtf import set_metadata as set_rtf_metadata
from calibre.ebooks.lrf.meta import set_metadata as set_lrf_metadata
from calibre.ebooks.metadata.epub import set_metadata as set_epub_metadata
from calibre.ebooks.metadata import MetaInformation
from calibre.utils.config import prefs
_METADATA_PRIORITIES = [
'html', 'htm', 'xhtml', 'xhtm',
'rtf', 'fb2', 'pdf', 'prc',
'rtf', 'fb2', 'pdf', 'prc', 'odt',
'epub', 'lit', 'lrf', 'mobi', 'rb', 'imp'
]
@ -64,6 +64,8 @@ def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False):
stream_type = 'html'
if stream_type in ('mobi', 'prc'):
stream_type = 'mobi'
if stream_type in ('odt', 'ods', 'odp', 'odg', 'odf'):
stream_type = 'odt'
opf = None
if hasattr(stream, 'name'):

View File

@ -0,0 +1,266 @@
#!/usr/bin/python
# -*- 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):
#
import zipfile, sys, re
import xml.sax.saxutils
from cStringIO import StringIO
from odf.namespaces import OFFICENS, DCNS, METANS
from calibre.ebooks.metadata import MetaInformation, string_to_authors
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'),
}
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)
class odfmetaparser(xml.sax.saxutils.XMLGenerator):
""" 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
"""
def __init__(self, deletefields={}, yieldfields={}, addfields={}):
self.deletefields = deletefields
self.yieldfields = yieldfields
self.addfields = addfields
self._mimetype = ''
self.output = MetaCollector()
self._data = []
self.seenfields = {}
xml.sax.saxutils.XMLGenerator.__init__(self, self.output, 'utf-8')
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 name == (METANS,u'user-defined'):
field = attrs.get((METANS,u'name'))
if field in self.deletefields:
self.output.dowrite = False
elif field in self.yieldfields:
del self.addfields[field]
xml.sax.saxutils.XMLGenerator.startElementNS(self, name, qname, attrs)
else:
xml.sax.saxutils.XMLGenerator.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 self.addfields.items():
if len(v) > 0:
if type(k) == type(''):
xml.sax.saxutils.XMLGenerator.startElementNS(self,(METANS,u'user-defined'),None,{(METANS,u'name'):k})
xml.sax.saxutils.XMLGenerator.characters(self, v)
xml.sax.saxutils.XMLGenerator.endElementNS(self, (METANS,u'user-defined'),None)
else:
xml.sax.saxutils.XMLGenerator.startElementNS(self, k, None, {})
xml.sax.saxutils.XMLGenerator.characters(self, v)
xml.sax.saxutils.XMLGenerator.endElementNS(self, k, None)
if isinstance(self._tag, tuple):
texttag = self._tag[1]
else:
texttag = self._tag
self.seenfields[texttag] = self.data()
if field in self.deletefields:
self.output.dowrite = True
else:
xml.sax.saxutils.XMLGenerator.endElementNS(self, name, qname)
def characters(self, content):
xml.sax.saxutils.XMLGenerator.characters(self, content)
self._data.append(content)
def meta(self):
return self.output.content()
def data(self):
return normalize(''.join(self._data))
def get_metadata(stream):
zin = zipfile.ZipFile(stream, 'r')
odfs = odfmetaparser()
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 1)
parser.setContentHandler(odfs)
content = zin.read('meta.xml')
parser.parse(StringIO(content))
data = odfs.seenfields
mi = MetaInformation(None, [])
if data.has_key('title'):
mi.title = data['title']
if data.has_key('creator'):
mi.authors = string_to_authors(data['creator'])
if data.has_key('description'):
mi.comments = data['description']
if data.has_key('language'):
mi.language = data['language']
if data.get('keywords', ''):
mi.tags = data['keywords'].split(',')
return mi
def main(args=sys.argv):
if len(args) != 2:
print 'Usage: %s file.odt'%args[0]
return 1
mi = get_metadata(open(args[1], 'rb'))
print mi
return 0
if __name__ == '__main__':
sys.exit(main())
#now = time.localtime()[:6]
#outputfile = "-"
#writemeta = False # Do we change any meta data?
#usenormalize = False
#
#try:
# opts, args = getopt.getopt(sys.argv[1:], "cdlI: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 == '-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')
#
#content = zin.read('meta.xml')
#parser.parse(StringIO(content))
#
#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()

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Handle the Open Document Format
'''

View File

@ -0,0 +1,72 @@
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Convert an ODT file into a Open Ebook
'''
import os, sys
from odf.odf2xhtml import ODF2XHTML
from calibre import CurrentDir, walk
from calibre.utils.zipfile import ZipFile
from calibre.utils.config import OptionParser
from calibre.ebooks.metadata.odt import get_metadata
from calibre.ebooks.metadata.opf2 import OPFCreator
class Extract(ODF2XHTML):
def extract_pictures(self, zf):
if not os.path.exists('Pictures'):
os.makedirs('Pictures')
for name in zf.namelist():
if name.startswith('Pictures'):
data = zf.read(name)
with open(name, 'wb') as f:
f.write(data)
def __call__(self, path, odir):
if not os.path.exists(odir):
os.makedirs(odir)
path = os.path.abspath(path)
with CurrentDir(odir):
print 'Extracting ODT file...'
html = self.odf2xhtml(path)
with open('index.html', 'wb') as f:
f.write(html.encode('utf-8'))
with open(path, 'rb') as f:
zf = ZipFile(f, 'r')
self.extract_pictures(zf)
f.seek(0)
mi = get_metadata(f)
if not mi.title:
mi.title = os.path.splitext(os.path.basename(path))
if not mi.authors:
mi.authors = [_('Unknown')]
opf = OPFCreator(os.path.abspath(os.getcwdu()), mi)
opf.create_manifest([(os.path.abspath(f), None) for f in walk(os.getcwd())])
opf.create_spine([os.path.abspath('index.html')])
with open('metadata.opf', 'wb') as f:
opf.render(f)
return os.path.abspath('metadata.opf')
def option_parser():
parser = OptionParser('%prog [options] file.odt')
parser.add_option('-o', '--output-dir', default='.',
help=_('The output directory. Defaults to the current directory.'))
return parser
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) < 2:
parser.print_help()
print 'No ODT file specified'
return 1
Extract()(args[1], os.path.abspath(opts.output_dir))
print 'Extracted to', os.path.abspath(opts.output_dir)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -24,11 +24,13 @@ entry_points = {
'imp-meta = calibre.ebooks.metadata.imp:main',
'rb-meta = calibre.ebooks.metadata.rb:main',
'opf-meta = calibre.ebooks.metadata.opf:main',
'odt-meta = calibre.ebooks.metadata.odt:main',
'epub-meta = calibre.ebooks.metadata.epub:main',
'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
'html2oeb = calibre.ebooks.html:main',
'html2epub = calibre.ebooks.epub.from_html:main',
'odt2oeb = calibre.ebooks.odt.to_oeb:main',
'markdown-calibre = calibre.ebooks.markdown.markdown:main',
'lit2lrf = calibre.ebooks.lrf.lit.convert_from:main',
'epub2lrf = calibre.ebooks.lrf.epub.convert_from:main',
@ -176,11 +178,12 @@ def setup_completion(fatal_errors):
from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop
from calibre.ebooks.epub.from_html import option_parser as html2epub
from calibre.ebooks.html import option_parser as html2oeb
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
from calibre.ebooks.epub.from_any import option_parser as any2epub
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2']
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt']
f = open_file('/etc/bash_completion.d/libprs500')
f.close()
os.remove(f.name)
@ -208,6 +211,7 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('imp-meta', metaop, ['imp']))
f.write(opts_and_exts('rb-meta', metaop, ['rb']))
f.write(opts_and_exts('opf-meta', metaop, ['opf']))
f.write(opts_and_exts('odt-meta', metaop, ['odt', 'ods', 'odf', 'odg', 'odp']))
f.write(opts_and_exts('epub-meta', epub_meta, ['epub']))
f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf']))
f.write(opts_and_exts('pdfrelow', pdfhtmlop, ['pdf']))
@ -220,6 +224,7 @@ def setup_completion(fatal_errors):
f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles))
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
f.write('''
_prs500_ls()
{

View File

@ -32,6 +32,8 @@ What formats does |app| support conversion to/from?
| | | | |
| | EPUB | ✔ | ✔ |
| | | | |
| | ODT | ✔ | ✔ |
| | | | |
| | HTML | ✔ | ✔ |
| | | | |
| **Input formats** | CBR | ✔ | ✔ |

0
src/odf/__init__.py Normal file
View File

61
src/odf/anim.py Normal file
View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import ANIMNS
from element import Element
# Autogenerated
def Animate(**args):
return Element(qname = (ANIMNS,'animate'), **args)
def Animatecolor(**args):
return Element(qname = (ANIMNS,'animateColor'), **args)
def Animatemotion(**args):
return Element(qname = (ANIMNS,'animateMotion'), **args)
def Animatetransform(**args):
return Element(qname = (ANIMNS,'animateTransform'), **args)
def Audio(**args):
return Element(qname = (ANIMNS,'audio'), **args)
def Command(**args):
return Element(qname = (ANIMNS,'command'), **args)
def Iterate(**args):
return Element(qname = (ANIMNS,'iterate'), **args)
def Par(**args):
return Element(qname = (ANIMNS,'par'), **args)
def Param(**args):
return Element(qname = (ANIMNS,'param'), **args)
def Seq(**args):
return Element(qname = (ANIMNS,'seq'), **args)
def Set(**args):
return Element(qname = (ANIMNS,'set'), **args)
def Transitionfilter(**args):
return Element(qname = (ANIMNS,'transitionFilter'), **args)

1444
src/odf/attrconverters.py Normal file

File diff suppressed because it is too large Load Diff

87
src/odf/chart.py Normal file
View File

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import CHARTNS
from element import Element
# Autogenerated
def Axis(**args):
return Element(qname = (CHARTNS,'axis'), **args)
def Categories(**args):
return Element(qname = (CHARTNS,'categories'), **args)
def Chart(**args):
return Element(qname = (CHARTNS,'chart'), **args)
def DataPoint(**args):
return Element(qname = (CHARTNS,'data-point'), **args)
def Domain(**args):
return Element(qname = (CHARTNS,'domain'), **args)
def ErrorIndicator(**args):
return Element(qname = (CHARTNS,'error-indicator'), **args)
def Floor(**args):
return Element(qname = (CHARTNS,'floor'), **args)
def Footer(**args):
return Element(qname = (CHARTNS,'footer'), **args)
def Grid(**args):
return Element(qname = (CHARTNS,'grid'), **args)
def Legend(**args):
return Element(qname = (CHARTNS,'legend'), **args)
def MeanValue(**args):
return Element(qname = (CHARTNS,'mean-value'), **args)
def PlotArea(**args):
return Element(qname = (CHARTNS,'plot-area'), **args)
def RegressionCurve(**args):
return Element(qname = (CHARTNS,'regression-curve'), **args)
def Series(**args):
return Element(qname = (CHARTNS,'series'), **args)
def StockGainMarker(**args):
return Element(qname = (CHARTNS,'stock-gain-marker'), **args)
def StockLossMarker(**args):
return Element(qname = (CHARTNS,'stock-loss-marker'), **args)
def StockRangeLine(**args):
return Element(qname = (CHARTNS,'stock-range-line'), **args)
def Subtitle(**args):
return Element(qname = (CHARTNS,'subtitle'), **args)
def SymbolImage(**args):
return Element(qname = (CHARTNS,'symbol-image'), **args)
def Title(**args):
return Element(qname = (CHARTNS,'title'), **args)
def Wall(**args):
return Element(qname = (CHARTNS,'wall'), **args)

39
src/odf/config.py Normal file
View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import CONFIGNS
from element import Element
# Autogenerated
def ConfigItem(**args):
return Element(qname = (CONFIGNS, 'config-item'), **args)
def ConfigItemMapEntry(**args):
return Element(qname = (CONFIGNS,'config-item-map-entry'), **args)
def ConfigItemMapIndexed(**args):
return Element(qname = (CONFIGNS,'config-item-map-indexed'), **args)
def ConfigItemMapNamed(**args):
return Element(qname = (CONFIGNS,'config-item-map-named'), **args)
def ConfigItemSet(**args):
return Element(qname = (CONFIGNS, 'config-item-set'), **args)

72
src/odf/dc.py Normal file
View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import DCNS
from element import Element
# Autogenerated
def Creator(**args):
return Element(qname = (DCNS,'creator'), **args)
def Date(**args):
return Element(qname = (DCNS,'date'), **args)
def Description(**args):
return Element(qname = (DCNS,'description'), **args)
def Language(**args):
return Element(qname = (DCNS,'language'), **args)
def Subject(**args):
return Element(qname = (DCNS,'subject'), **args)
def Title(**args):
return Element(qname = (DCNS,'title'), **args)
# The following complete the Dublin Core elements, but there is no
# guarantee a compliant implementation of OpenDocument will preserve
# these elements
#def Contributor(**args):
# return Element(qname = (DCNS,'contributor'), **args)
#def Coverage(**args):
# return Element(qname = (DCNS,'coverage'), **args)
#def Format(**args):
# return Element(qname = (DCNS,'format'), **args)
#def Identifier(**args):
# return Element(qname = (DCNS,'identifier'), **args)
#def Publisher(**args):
# return Element(qname = (DCNS,'publisher'), **args)
#def Relation(**args):
# return Element(qname = (DCNS,'relation'), **args)
#def Rights(**args):
# return Element(qname = (DCNS,'rights'), **args)
#def Source(**args):
# return Element(qname = (DCNS,'source'), **args)
#def Type(**args):
# return Element(qname = (DCNS,'type'), **args)

43
src/odf/dr3d.py Normal file
View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import DR3DNS
from element import Element
from draw import StyleRefElement
# Autogenerated
def Cube(**args):
return StyleRefElement(qname = (DR3DNS,'cube'), **args)
def Extrude(**args):
return StyleRefElement(qname = (DR3DNS,'extrude'), **args)
def Light(Element):
return StyleRefElement(qname = (DR3DNS,'light'), **args)
def Rotate(**args):
return StyleRefElement(qname = (DR3DNS,'rotate'), **args)
def Scene(**args):
return StyleRefElement(qname = (DR3DNS,'scene'), **args)
def Sphere(**args):
return StyleRefElement(qname = (DR3DNS,'sphere'), **args)

182
src/odf/draw.py Normal file
View File

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import DRAWNS, STYLENS, PRESENTATIONNS
from element import Element
def StyleRefElement(stylename=None, classnames=None, **args):
qattrs = {}
if stylename is not None:
f = stylename.getAttrNS(STYLENS, 'family')
if f == 'graphic':
qattrs[(DRAWNS,u'style-name')]= stylename
elif f == 'presentation':
qattrs[(PRESENTATIONNS,u'style-name')]= stylename
else:
raise ValueError, "Style's family must be either 'graphic' or 'presentation'"
if classnames is not None:
f = classnames[0].getAttrNS(STYLENS, 'family')
if f == 'graphic':
qattrs[(DRAWNS,u'class-names')]= classnames
elif f == 'presentation':
qattrs[(PRESENTATIONNS,u'class-names')]= classnames
else:
raise ValueError, "Style's family must be either 'graphic' or 'presentation'"
return Element(qattributes=qattrs, **args)
def DrawElement(name=None, **args):
e = Element(name=name, **args)
if not args.has_key('displayname'):
e.setAttrNS(DRAWNS,'display-name', name)
return e
# Autogenerated
def A(**args):
return Element(qname = (DRAWNS,'a'), **args)
def Applet(**args):
return Element(qname = (DRAWNS,'applet'), **args)
def AreaCircle(**args):
return Element(qname = (DRAWNS,'area-circle'), **args)
def AreaPolygon(**args):
return Element(qname = (DRAWNS,'area-polygon'), **args)
def AreaRectangle(**args):
return Element(qname = (DRAWNS,'area-rectangle'), **args)
def Caption(**args):
return StyleRefElement(qname = (DRAWNS,'caption'), **args)
def Circle(**args):
return StyleRefElement(qname = (DRAWNS,'circle'), **args)
def Connector(**args):
return StyleRefElement(qname = (DRAWNS,'connector'), **args)
def ContourPath(**args):
return Element(qname = (DRAWNS,'contour-path'), **args)
def ContourPolygon(**args):
return Element(qname = (DRAWNS,'contour-polygon'), **args)
def Control(**args):
return StyleRefElement(qname = (DRAWNS,'control'), **args)
def CustomShape(**args):
return StyleRefElement(qname = (DRAWNS,'custom-shape'), **args)
def Ellipse(**args):
return StyleRefElement(qname = (DRAWNS,'ellipse'), **args)
def EnhancedGeometry(**args):
return Element(qname = (DRAWNS,'enhanced-geometry'), **args)
def Equation(**args):
return Element(qname = (DRAWNS,'equation'), **args)
def FillImage(**args):
return DrawElement(qname = (DRAWNS,'fill-image'), **args)
def FloatingFrame(**args):
return Element(qname = (DRAWNS,'floating-frame'), **args)
def Frame(**args):
return StyleRefElement(qname = (DRAWNS,'frame'), **args)
def G(**args):
return StyleRefElement(qname = (DRAWNS,'g'), **args)
def GluePoint(**args):
return Element(qname = (DRAWNS,'glue-point'), **args)
def Gradient(**args):
return DrawElement(qname = (DRAWNS,'gradient'), **args)
def Handle(**args):
return Element(qname = (DRAWNS,'handle'), **args)
def Hatch(**args):
return DrawElement(qname = (DRAWNS,'hatch'), **args)
def Image(**args):
return Element(qname = (DRAWNS,'image'), **args)
def ImageMap(**args):
return Element(qname = (DRAWNS,'image-map'), **args)
def Layer(**args):
return Element(qname = (DRAWNS,'layer'), **args)
def LayerSet(**args):
return Element(qname = (DRAWNS,'layer-set'), **args)
def Line(**args):
return StyleRefElement(qname = (DRAWNS,'line'), **args)
def Marker(**args):
return DrawElement(qname = (DRAWNS,'marker'), **args)
def Measure(**args):
return StyleRefElement(qname = (DRAWNS,'measure'), **args)
def Object(**args):
return Element(qname = (DRAWNS,'object'), **args)
def ObjectOle(**args):
return Element(qname = (DRAWNS,'object-ole'), **args)
def Opacity(**args):
return DrawElement(qname = (DRAWNS,'opacity'), **args)
def Page(**args):
return Element(qname = (DRAWNS,'page'), **args)
def PageThumbnail(**args):
return StyleRefElement(qname = (DRAWNS,'page-thumbnail'), **args)
def Param(**args):
return Element(qname = (DRAWNS,'param'), **args)
def Path(**args):
return StyleRefElement(qname = (DRAWNS,'path'), **args)
def Plugin(**args):
return Element(qname = (DRAWNS,'plugin'), **args)
def Polygon(**args):
return StyleRefElement(qname = (DRAWNS,'polygon'), **args)
def Polyline(**args):
return StyleRefElement(qname = (DRAWNS,'polyline'), **args)
def Rect(**args):
return StyleRefElement(qname = (DRAWNS,'rect'), **args)
def RegularPolygon(**args):
return StyleRefElement(qname = (DRAWNS,'regular-polygon'), **args)
def StrokeDash(**args):
return DrawElement(qname = (DRAWNS,'stroke-dash'), **args)
def TextBox(**args):
return Element(qname = (DRAWNS,'text-box'), **args)

103
src/odf/easyliststyle.py Normal file
View File

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Create a <text:list-style> element from a text string.
# Copyright (C) 2008 J. David Eisenberg
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, 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 re
from style import Style, TextProperties, ListLevelProperties
from text import ListStyle,ListLevelStyleNumber,ListLevelStyleBullet
"""
Create a <text:list-style> element from a string or array.
List styles require a lot of code to create one level at a time.
These routines take a string and delimiter, or a list of
strings, and creates a <text:list-style> element for you.
Each item in the string (or array) represents a list level
* style for levels 1-10.</p>
*
* <p>If an item contains <code>1</code>, <code>I</code>,
* <code>i</code>, <code>A</code>, or <code>a</code>, then it is presumed
* to be a numbering style; otherwise it is a bulleted style.</p>
"""
_MAX_LIST_LEVEL = 10
SHOW_ALL_LEVELS = True
SHOW_ONE_LEVEL = False
def styleFromString(name, specifiers, delim, spacing, showAllLevels):
specArray = specifiers.split(delim)
return styleFromList( name, specArray, spacing, showAllLevels )
def styleFromList( styleName, specArray, spacing, showAllLevels):
bullet = ""
numPrefix = ""
numSuffix = ""
numberFormat = ""
cssLengthNum = 0
cssLengthUnits = ""
numbered = False
displayLevels = 0
listStyle = ListStyle(name=styleName)
numFormatPattern = re.compile("([1IiAa])")
cssLengthPattern = re.compile("([^a-z]+)\\s*([a-z]+)?")
m = cssLengthPattern.search( spacing )
if (m != None):
cssLengthNum = float(m.group(1))
if (m.lastindex == 2):
cssLengthUnits = m.group(2)
i = 0
while i < len(specArray):
specification = specArray[i]
m = numFormatPattern.search(specification)
if (m != None):
numberFormat = m.group(1)
numPrefix = specification[0:m.start(1)]
numSuffix = specification[m.end(1):]
bullet = ""
numbered = True
if (showAllLevels):
displayLevels = i + 1
else:
displayLevels = 1
else: # it's a bullet style
bullet = specification
numPrefix = ""
numSuffix = ""
numberFormat = ""
displayLevels = 1
numbered = False
if (numbered):
lls = ListLevelStyleNumber(level=(i+1))
if (numPrefix != ''):
lls.setAttribute('numprefix', numPrefix)
if (numSuffix != ''):
lls.setAttribute('numsuffix', numSuffix)
lls.setAttribute('displaylevels', displayLevels)
else:
lls = ListLevelStyleBullet(level=(i+1),bulletchar=bullet[0])
llp = ListLevelProperties()
llp.setAttribute('spacebefore', str(cssLengthNum * (i+1)) + cssLengthUnits)
llp.setAttribute('minlabelwidth', str(cssLengthNum) + cssLengthUnits)
lls.addElement( llp )
listStyle.addElement(lls)
i += 1
return listStyle
# vim: set expandtab sw=4 :

454
src/odf/element.py Normal file
View File

@ -0,0 +1,454 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2007-2008 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
# Note: This script has copied a lot of text from xml.dom.minidom.
# Whatever license applies to that file also applies to this file.
#
import xml.dom
from xml.dom.minicompat import *
from namespaces import nsdict
import grammar
from attrconverters import AttrConverters
# The following code is pasted form xml.sax.saxutils
# Tt makes it possible to run the code without the xml sax package installed
# To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts
def _escape(data, entities={}):
""" Escape &, <, and > in a string of data.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
data = data.replace("&", "&amp;")
data = data.replace("<", "&lt;")
data = data.replace(">", "&gt;")
for chars, entity in entities.items():
data = data.replace(chars, entity)
return data
def _quoteattr(data, entities={}):
""" Escape and quote an attribute value.
Escape &, <, and > in a string of data, then quote it for use as
an attribute value. The \" character will be escaped as well, if
necessary.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
data = _escape(data, entities)
if '"' in data:
if "'" in data:
data = '"%s"' % data.replace('"', "&quot;")
else:
data = "'%s'" % data
else:
data = '"%s"' % data
return data
def _nssplit(qualifiedName):
""" Split a qualified name into namespace part and local part. """
fields = qualifiedName.split(':', 1)
if len(fields) == 2:
return fields
else:
return (None, fields[0])
def _nsassign(namespace):
return nsdict.setdefault(namespace,"ns" + str(len(nsdict)))
# Exceptions
class IllegalChild(StandardError):
""" Complains if you add an element to a parent where it is not allowed """
class IllegalText(StandardError):
""" Complains if you add text or cdata to an element where it is not allowed """
class Node(xml.dom.Node):
""" super class for more specific nodes """
parentNode = None
nextSibling = None
previousSibling = None
def hasChildNodes(self):
""" Tells whether this element has any children; text nodes,
subelements, whatever.
"""
if self.childNodes:
return True
else:
return False
def _get_childNodes(self):
return self.childNodes
def _get_firstChild(self):
if self.childNodes:
return self.childNodes[0]
def _get_lastChild(self):
if self.childNodes:
return self.childNodes[-1]
def insertBefore(self, newChild, refChild):
if newChild.nodeType not in self._child_node_types:
raise IllegalChild, "%s cannot be child of %s" % (newChild.tagName, self.tagName)
if newChild.parentNode is not None:
newChild.parentNode.removeChild(newChild)
if refChild is None:
self.appendChild(newChild)
else:
try:
index = self.childNodes.index(refChild)
except ValueError:
raise xml.dom.NotFoundErr()
self.childNodes.insert(index, newChild)
newChild.nextSibling = refChild
refChild.previousSibling = newChild
if index:
node = self.childNodes[index-1]
node.nextSibling = newChild
newChild.previousSibling = node
else:
newChild.previousSibling = None
newChild.parentNode = self
return newChild
def appendChild(self, node):
if node.nodeType == self.DOCUMENT_FRAGMENT_NODE:
for c in tuple(node.childNodes):
self.appendChild(c)
### The DOM does not clearly specify what to return in this case
return node
if node.nodeType not in self._child_node_types:
raise IllegalChild, "<%s> is not allowed in %s" % ( node.tagName, self.tagName)
if node.parentNode is not None:
node.parentNode.removeChild(node)
_append_child(self, node)
node.nextSibling = None
return node
def removeChild(self, oldChild):
#FIXME: update ownerDocument.element_dict or find other solution
try:
self.childNodes.remove(oldChild)
except ValueError:
raise xml.dom.NotFoundErr()
if oldChild.nextSibling is not None:
oldChild.nextSibling.previousSibling = oldChild.previousSibling
if oldChild.previousSibling is not None:
oldChild.previousSibling.nextSibling = oldChild.nextSibling
oldChild.nextSibling = oldChild.previousSibling = None
if self.ownerDocument:
self.ownerDocument.clear_caches()
oldChild.parentNode = None
return oldChild
defproperty(Node, "firstChild", doc="First child node, or None.")
defproperty(Node, "lastChild", doc="Last child node, or None.")
def _append_child(self, node):
# fast path with less checks; usable by DOM builders if careful
childNodes = self.childNodes
if childNodes:
last = childNodes[-1]
node.__dict__["previousSibling"] = last
last.__dict__["nextSibling"] = node
childNodes.append(node)
node.__dict__["parentNode"] = self
class Childless:
"""Mixin that makes childless-ness easy to implement and avoids
the complexity of the Node methods that deal with children.
"""
attributes = None
childNodes = EmptyNodeList()
firstChild = None
lastChild = None
def _get_firstChild(self):
return None
def _get_lastChild(self):
return None
def appendChild(self, node):
raise xml.dom.HierarchyRequestErr(
self.tagName + " nodes cannot have children")
def hasChildNodes(self):
return False
def insertBefore(self, newChild, refChild):
raise xml.dom.HierarchyRequestErr(
self.tagName + " nodes do not have children")
def removeChild(self, oldChild):
raise xml.dom.NotFoundErr(
self.tagName + " nodes do not have children")
def replaceChild(self, newChild, oldChild):
raise xml.dom.HierarchyRequestErr(
self.tagName + " nodes do not have children")
class Text(Childless, Node):
nodeType = Node.TEXT_NODE
tagName = "Text"
def __init__(self, data):
self.data = data
def __str__(self):
return self.data
def toXml(self,level,f):
""" Write XML in UTF-8 """
if self.data:
f.write(_escape(unicode(self.data).encode('utf-8')))
class CDATASection(Childless, Text):
nodeType = Node.CDATA_SECTION_NODE
def toXml(self,level,f):
if self.data:
f.write('<![CDATA[%s]]>' % self.data)
class Element(Node):
""" Creates a arbitrary element and is intended to be subclassed not used on its own.
This element is the base of every element it defines a class which resembles
a xml-element. The main advantage of this kind of implementation is that you don't
have to create a toXML method for every different object. Every element
consists of an attribute, optional subelements, optional text and optional cdata.
"""
nodeType = Node.ELEMENT_NODE
namespaces = {} # Due to shallow copy this is a static variable
_child_node_types = (Node.ELEMENT_NODE,
Node.PROCESSING_INSTRUCTION_NODE,
Node.COMMENT_NODE,
Node.TEXT_NODE,
Node.CDATA_SECTION_NODE,
Node.ENTITY_REFERENCE_NODE)
def __init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, **args):
if qname is not None:
self.qname = qname
assert(hasattr(self, 'qname'))
self.ownerDocument = None
self.childNodes=[]
self.allowed_children = grammar.allowed_children.get(self.qname)
namespace = self.qname[0]
prefix = _nsassign(namespace)
if not self.namespaces.has_key(namespace):
self.namespaces[namespace] = prefix
self.tagName = prefix + ":" + self.qname[1]
if text is not None:
self.addText(text)
if cdata is not None:
self.addCDATA(cdata)
allowed_attrs = self.allowed_attributes()
if allowed_attrs is not None:
allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
self.attributes={}
# Load the attributes from the 'attributes' argument
if attributes:
for attr, value in attributes.items():
self.setAttribute(attr, value)
# Load the qualified attributes
if qattributes:
for attr, value in qattributes.items():
self.setAttrNS(attr[0], attr[1], value)
if allowed_attrs is not None:
# Load the attributes from the 'args' argument
for arg in args.keys():
self.setAttribute(arg, args[arg])
else:
for arg in args.keys(): # If any attribute is allowed
self.attributes[arg]=args[arg]
if not check_grammar:
return
# Test that all mandatory attributes have been added.
required = grammar.required_attributes.get(self.qname)
if required:
for r in required:
if self.getAttrNS(r[0],r[1]) is None:
raise AttributeError, "Required attribute missing: %s in <%s>" % (r[1].lower().replace('-',''), self.tagName)
def allowed_attributes(self):
return grammar.allowed_attributes.get(self.qname)
def _setOwnerDoc(self, element):
element.ownerDocument = self.ownerDocument
for child in element.childNodes:
self._setOwnerDoc(child)
def addElement(self, element, check_grammar=True):
""" adds an element to an Element
Element.addElement(Element)
"""
if check_grammar and self.allowed_children is not None:
if element.qname not in self.allowed_children:
raise IllegalChild, "<%s> is not allowed in <%s>" % ( element.tagName, self.tagName)
self.appendChild(element)
self._setOwnerDoc(element)
if self.ownerDocument:
self.ownerDocument.rebuild_caches(element)
def addText(self, text, check_grammar=True):
if check_grammar and self.qname not in grammar.allows_text:
raise IllegalText, "The <%s> element does not allow text" % self.tagName
else:
if text != '':
self.appendChild(Text(text))
def addCDATA(self, cdata, check_grammar=True):
if check_grammar and self.qname not in grammar.allows_text:
raise IllegalText, "The <%s> element does not allow text" % self.tagName
else:
self.appendChild(CDATASection(cdata))
def removeAttribute(self, attr, check_grammar=True):
""" Removes an attribute by name. """
allowed_attrs = self.allowed_attributes()
if allowed_attrs is None:
if type(attr) == type(()):
prefix, localname = attr
self.removeAttrNS(prefix, localname)
else:
raise AttributeError, "Unable to add simple attribute - use (namespace, localpart)"
else:
# Construct a list of allowed arguments
allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
if check_grammar and attr not in allowed_args:
raise AttributeError, "Attribute %s is not allowed in <%s>" % ( attr, self.tagName)
i = allowed_args.index(attr)
self.removeAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
def setAttribute(self, attr, value, check_grammar=True):
""" Add an attribute to the element
This is sort of a convenience method. All attributes in ODF have
namespaces. The library knows what attributes are legal and then allows
the user to provide the attribute as a keyword argument and the
library will add the correct namespace.
Must overwrite, If attribute already exists.
"""
allowed_attrs = self.allowed_attributes()
if allowed_attrs is None:
if type(attr) == type(()):
prefix, localname = attr
self.setAttrNS(prefix, localname, value)
else:
raise AttributeError, "Unable to add simple attribute - use (namespace, localpart)"
else:
# Construct a list of allowed arguments
allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
if check_grammar and attr not in allowed_args:
raise AttributeError, "Attribute %s is not allowed in <%s>" % ( attr, self.tagName)
i = allowed_args.index(attr)
self.setAttrNS(allowed_attrs[i][0], allowed_attrs[i][1], value)
def setAttrNS(self, namespace, localpart, value):
""" Add an attribute to the element
In case you need to add an attribute the library doesn't know about
then you must provide the full qualified name
It will not check that the attribute is legal according to the schema.
Must overwrite, If attribute already exists.
"""
allowed_attrs = self.allowed_attributes()
prefix = _nsassign(namespace)
if not self.namespaces.has_key(namespace):
self.namespaces[namespace] = prefix
# if allowed_attrs and (namespace, localpart) not in allowed_attrs:
# raise AttributeError, "Attribute %s:%s is not allowed in element <%s>" % ( prefix, localpart, self.tagName)
c = AttrConverters()
self.attributes[prefix + ":" + localpart] = c.convert((namespace, localpart), value, self.qname)
def getAttrNS(self, namespace, localpart):
prefix = _nsassign(namespace)
if not self.namespaces.has_key(namespace):
self.namespaces[namespace] = prefix
return self.attributes.get(prefix + ":" + localpart)
def removeAttrNS(self, namespace, localpart):
prefix = _nsassign(namespace)
if not self.namespaces.has_key(namespace):
self.namespaces[namespace] = prefix
del self.attributes[prefix + ":" + localpart]
def getAttribute(self, attr):
allowed_attrs = self.allowed_attributes()
if allowed_attrs is None:
if type(attr) == type(()):
prefix, localname = attr
return self.getAttrNS(prefix, localname)
else:
raise AttributeError, "Unable to get simple attribute - use (namespace, localpart)"
else:
# Construct a list of allowed arguments
allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
i = allowed_args.index(attr)
return self.getAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
def write_open_tag(self, level, f):
f.write('<'+self.tagName)
if level == 0:
for namespace, prefix in self.namespaces.items():
f.write(' xmlns:' + prefix + '="'+ _escape(str(namespace))+'"')
for attkey in self.attributes.keys():
f.write(' '+_escape(str(attkey))+'='+_quoteattr(unicode(self.attributes[attkey]).encode('utf-8')))
f.write('>')
def write_close_tag(self, level, f):
f.write('</'+self.tagName+'>')
def toXml(self, level, f):
""" Generate XML stream out of the tree structure """
f.write('<'+self.tagName)
if level == 0:
for namespace, prefix in self.namespaces.items():
f.write(' xmlns:' + prefix + '="'+ _escape(str(namespace))+'"')
for attkey in self.attributes.keys():
f.write(' '+_escape(str(attkey))+'='+_quoteattr(unicode(self.attributes[attkey]).encode('utf-8')))
if self.childNodes:
f.write('>')
for element in self.childNodes:
element.toXml(level+1,f)
f.write('</'+self.tagName+'>')
else:
f.write('/>')
def _getElementsByObj(self, obj, accumulator):
if self.qname == obj.qname:
accumulator.append(self)
for e in self.childNodes:
if e.nodeType == Node.ELEMENT_NODE:
accumulator = e._getElementsByObj(obj, accumulator)
return accumulator
def getElementsByType(self, element):
obj = element(check_grammar=False)
return self._getElementsByObj(obj,[])

330
src/odf/elementtypes.py Normal file
View File

@ -0,0 +1,330 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2008 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import *
# Inline element don't cause a box
# They are analogous to the HTML elements SPAN, B, I etc.
inline_elements = (
(TEXTNS,u'a'),
(TEXTNS,u'author-initials'),
(TEXTNS,u'author-name'),
(TEXTNS,u'bibliography-mark'),
(TEXTNS,u'bookmark-ref'),
(TEXTNS,u'chapter'),
(TEXTNS,u'character-count'),
(TEXTNS,u'conditional-text'),
(TEXTNS,u'creation-date'),
(TEXTNS,u'creation-time'),
(TEXTNS,u'creator'),
(TEXTNS,u'database-display'),
(TEXTNS,u'database-name'),
(TEXTNS,u'database-next'),
(TEXTNS,u'database-row-number'),
(TEXTNS,u'database-row-select'),
(TEXTNS,u'date'),
(TEXTNS,u'dde-connection'),
(TEXTNS,u'description'),
(TEXTNS,u'editing-cycles'),
(TEXTNS,u'editing-duration'),
(TEXTNS,u'execute-macro'),
(TEXTNS,u'expression'),
(TEXTNS,u'file-name'),
(TEXTNS,u'hidden-paragraph'),
(TEXTNS,u'hidden-text'),
(TEXTNS,u'image-count'),
(TEXTNS,u'initial-creator'),
(TEXTNS,u'keywords'),
(TEXTNS,u'measure'),
(TEXTNS,u'modification-date'),
(TEXTNS,u'modification-time'),
(TEXTNS,u'note-ref'),
(TEXTNS,u'object-count'),
(TEXTNS,u'page-continuation'),
(TEXTNS,u'page-count'),
(TEXTNS,u'page-number'),
(TEXTNS,u'page-variable-get'),
(TEXTNS,u'page-variable-set'),
(TEXTNS,u'paragraph-count'),
(TEXTNS,u'placeholder'),
(TEXTNS,u'print-date'),
(TEXTNS,u'printed-by'),
(TEXTNS,u'print-time'),
(TEXTNS,u'reference-ref'),
(TEXTNS,u'ruby'),
(TEXTNS,u'ruby-base'),
(TEXTNS,u'ruby-text'),
(TEXTNS,u'script'),
(TEXTNS,u'sender-city'),
(TEXTNS,u'sender-company'),
(TEXTNS,u'sender-country'),
(TEXTNS,u'sender-email'),
(TEXTNS,u'sender-fax'),
(TEXTNS,u'sender-firstname'),
(TEXTNS,u'sender-initials'),
(TEXTNS,u'sender-lastname'),
(TEXTNS,u'sender-phone-private'),
(TEXTNS,u'sender-phone-work'),
(TEXTNS,u'sender-position'),
(TEXTNS,u'sender-postal-code'),
(TEXTNS,u'sender-state-or-province'),
(TEXTNS,u'sender-street'),
(TEXTNS,u'sender-title'),
(TEXTNS,u'sequence'),
(TEXTNS,u'sequence-ref'),
(TEXTNS,u'sheet-name'),
(TEXTNS,u'span'),
(TEXTNS,u'subject'),
(TEXTNS,u'table-count'),
(TEXTNS,u'table-formula'),
(TEXTNS,u'template-name'),
(TEXTNS,u'text-input'),
(TEXTNS,u'time'),
(TEXTNS,u'title'),
(TEXTNS,u'user-defined'),
(TEXTNS,u'user-field-get'),
(TEXTNS,u'user-field-input'),
(TEXTNS,u'variable-get'),
(TEXTNS,u'variable-input'),
(TEXTNS,u'variable-set'),
(TEXTNS,u'word-count'),
)
struct_elements = (
(CONFIGNS,'config-item-set'),
(TABLENS,u'table-cell'),
)
# It is almost impossible to determine what elements are block elements.
# There are so many that don't fit the form
block_elements = (
(TEXTNS,u'h'),
(TEXTNS,u'p'),
(TEXTNS,u'list'),
(TEXTNS,u'list-item'),
(TEXTNS,u'section'),
)
declarative_elements = (
(OFFICENS,u'font-face-decls'),
(PRESENTATIONNS,u'date-time-decl'),
(PRESENTATIONNS,u'footer-decl'),
(PRESENTATIONNS,u'header-decl'),
(TABLENS,u'table-template'),
(TEXTNS,u'alphabetical-index-entry-template'),
(TEXTNS,u'alphabetical-index-source'),
(TEXTNS,u'bibliography-entry-template'),
(TEXTNS,u'bibliography-source'),
(TEXTNS,u'dde-connection-decls'),
(TEXTNS,u'illustration-index-entry-template'),
(TEXTNS,u'illustration-index-source'),
(TEXTNS,u'index-source-styles'),
(TEXTNS,u'index-title-template'),
(TEXTNS,u'note-continuation-notice-backward'),
(TEXTNS,u'note-continuation-notice-forward'),
(TEXTNS,u'notes-configuration'),
(TEXTNS,u'object-index-entry-template'),
(TEXTNS,u'object-index-source'),
(TEXTNS,u'sequence-decls'),
(TEXTNS,u'table-index-entry-template'),
(TEXTNS,u'table-index-source'),
(TEXTNS,u'table-of-content-entry-template'),
(TEXTNS,u'table-of-content-source'),
(TEXTNS,u'user-field-decls'),
(TEXTNS,u'user-index-entry-template'),
(TEXTNS,u'user-index-source'),
(TEXTNS,u'variable-decls'),
)
empty_elements = (
(ANIMNS,u'animate'),
(ANIMNS,u'animateColor'),
(ANIMNS,u'animateMotion'),
(ANIMNS,u'animateTransform'),
(ANIMNS,u'audio'),
(ANIMNS,u'param'),
(ANIMNS,u'set'),
(ANIMNS,u'transitionFilter'),
(CHARTNS,u'categories'),
(CHARTNS,u'data-point'),
(CHARTNS,u'domain'),
(CHARTNS,u'error-indicator'),
(CHARTNS,u'floor'),
(CHARTNS,u'grid'),
(CHARTNS,u'legend'),
(CHARTNS,u'mean-value'),
(CHARTNS,u'regression-curve'),
(CHARTNS,u'stock-gain-marker'),
(CHARTNS,u'stock-loss-marker'),
(CHARTNS,u'stock-range-line'),
(CHARTNS,u'symbol-image'),
(CHARTNS,u'wall'),
(DR3DNS,u'cube'),
(DR3DNS,u'extrude'),
(DR3DNS,u'light'),
(DR3DNS,u'rotate'),
(DR3DNS,u'sphere'),
(DRAWNS,u'contour-path'),
(DRAWNS,u'contour-polygon'),
(DRAWNS,u'equation'),
(DRAWNS,u'fill-image'),
(DRAWNS,u'floating-frame'),
(DRAWNS,u'glue-point'),
(DRAWNS,u'gradient'),
(DRAWNS,u'handle'),
(DRAWNS,u'hatch'),
(DRAWNS,u'layer'),
(DRAWNS,u'marker'),
(DRAWNS,u'opacity'),
(DRAWNS,u'page-thumbnail'),
(DRAWNS,u'param'),
(DRAWNS,u'stroke-dash'),
(FORMNS,u'connection-resource'),
(FORMNS,u'list-value'),
(FORMNS,u'property'),
(MANIFESTNS,u'algorithm'),
(MANIFESTNS,u'key-derivation'),
(METANS,u'auto-reload'),
(METANS,u'document-statistic'),
(METANS,u'hyperlink-behaviour'),
(METANS,u'template'),
(NUMBERNS,u'am-pm'),
(NUMBERNS,u'boolean'),
(NUMBERNS,u'day'),
(NUMBERNS,u'day-of-week'),
(NUMBERNS,u'era'),
(NUMBERNS,u'fraction'),
(NUMBERNS,u'hours'),
(NUMBERNS,u'minutes'),
(NUMBERNS,u'month'),
(NUMBERNS,u'quarter'),
(NUMBERNS,u'scientific-number'),
(NUMBERNS,u'seconds'),
(NUMBERNS,u'text-content'),
(NUMBERNS,u'week-of-year'),
(NUMBERNS,u'year'),
(OFFICENS,u'dde-source'),
(PRESENTATIONNS,u'date-time'),
(PRESENTATIONNS,u'footer'),
(PRESENTATIONNS,u'header'),
(PRESENTATIONNS,u'placeholder'),
(PRESENTATIONNS,u'play'),
(PRESENTATIONNS,u'show'),
(PRESENTATIONNS,u'sound'),
(SCRIPTNS,u'event-listener'),
(STYLENS,u'column'),
(STYLENS,u'column-sep'),
(STYLENS,u'drop-cap'),
(STYLENS,u'footnote-sep'),
(STYLENS,u'list-level-properties'),
(STYLENS,u'map'),
(STYLENS,u'ruby-properties'),
(STYLENS,u'table-column-properties'),
(STYLENS,u'tab-stop'),
(STYLENS,u'text-properties'),
(SVGNS,u'definition-src'),
(SVGNS,u'font-face-format'),
(SVGNS,u'font-face-name'),
(SVGNS,u'stop'),
(TABLENS,u'body'),
(TABLENS,u'cell-address'),
(TABLENS,u'cell-range-source'),
(TABLENS,u'change-deletion'),
(TABLENS,u'consolidation'),
(TABLENS,u'database-source-query'),
(TABLENS,u'database-source-sql'),
(TABLENS,u'database-source-table'),
(TABLENS,u'data-pilot-display-info'),
(TABLENS,u'data-pilot-field-reference'),
(TABLENS,u'data-pilot-group-member'),
(TABLENS,u'data-pilot-layout-info'),
(TABLENS,u'data-pilot-member'),
(TABLENS,u'data-pilot-sort-info'),
(TABLENS,u'data-pilot-subtotal'),
(TABLENS,u'dependency'),
(TABLENS,u'error-macro'),
(TABLENS,u'even-columns'),
(TABLENS,u'even-rows'),
(TABLENS,u'filter-condition'),
(TABLENS,u'first-column'),
(TABLENS,u'first-row'),
(TABLENS,u'highlighted-range'),
(TABLENS,u'insertion-cut-off'),
(TABLENS,u'iteration'),
(TABLENS,u'label-range'),
(TABLENS,u'last-column'),
(TABLENS,u'last-row'),
(TABLENS,u'movement-cut-off'),
(TABLENS,u'named-expression'),
(TABLENS,u'named-range'),
(TABLENS,u'null-date'),
(TABLENS,u'odd-columns'),
(TABLENS,u'odd-rows'),
(TABLENS,u'operation'),
(TABLENS,u'scenario'),
(TABLENS,u'sort-by'),
(TABLENS,u'sort-groups'),
(TABLENS,u'source-range-address'),
(TABLENS,u'source-service'),
(TABLENS,u'subtotal-field'),
(TABLENS,u'table-column'),
(TABLENS,u'table-source'),
(TABLENS,u'target-range-address'),
(TEXTNS,u'alphabetical-index-auto-mark-file'),
(TEXTNS,u'alphabetical-index-mark'),
(TEXTNS,u'alphabetical-index-mark-end'),
(TEXTNS,u'alphabetical-index-mark-start'),
(TEXTNS,u'bookmark'),
(TEXTNS,u'bookmark-end'),
(TEXTNS,u'bookmark-start'),
(TEXTNS,u'change'),
(TEXTNS,u'change-end'),
(TEXTNS,u'change-start'),
(TEXTNS,u'dde-connection-decl'),
(TEXTNS,u'index-entry-bibliography'),
(TEXTNS,u'index-entry-chapter'),
(TEXTNS,u'index-entry-link-end'),
(TEXTNS,u'index-entry-link-start'),
(TEXTNS,u'index-entry-page-number'),
(TEXTNS,u'index-entry-tab-stop'),
(TEXTNS,u'index-entry-text'),
(TEXTNS,u'index-source-style'),
(TEXTNS,u'line-break'),
(TEXTNS,u'page'),
(TEXTNS,u'reference-mark'),
(TEXTNS,u'reference-mark-end'),
(TEXTNS,u'reference-mark-start'),
(TEXTNS,u's'),
(TEXTNS,u'section-source'),
(TEXTNS,u'sequence-decl'),
(TEXTNS,u'soft-page-break'),
(TEXTNS,u'sort-key'),
(TEXTNS,u'tab'),
(TEXTNS,u'toc-mark'),
(TEXTNS,u'toc-mark-end'),
(TEXTNS,u'toc-mark-start'),
(TEXTNS,u'user-field-decl'),
(TEXTNS,u'user-index-mark'),
(TEXTNS,u'user-index-mark-end'),
(TEXTNS,u'user-index-mark-start'),
(TEXTNS,u'variable-decl')
)

115
src/odf/form.py Normal file
View File

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import FORMNS
from element import Element
# Autogenerated
def Button(**args):
return Element(qname = (FORMNS,'button'), **args)
def Checkbox(**args):
return Element(qname = (FORMNS,'checkbox'), **args)
def Column(**args):
return Element(qname = (FORMNS,'column'), **args)
def Combobox(**args):
return Element(qname = (FORMNS,'combobox'), **args)
def ConnectionResource(**args):
return Element(qname = (FORMNS,'connection-resource'), **args)
def Date(**args):
return Element(qname = (FORMNS,'date'), **args)
def File(**args):
return Element(qname = (FORMNS,'file'), **args)
def FixedText(**args):
return Element(qname = (FORMNS,'fixed-text'), **args)
def Form(**args):
return Element(qname = (FORMNS,'form'), **args)
def FormattedText(**args):
return Element(qname = (FORMNS,'formatted-text'), **args)
def Frame(**args):
return Element(qname = (FORMNS,'frame'), **args)
def GenericControl(**args):
return Element(qname = (FORMNS,'generic-control'), **args)
def Grid(**args):
return Element(qname = (FORMNS,'grid'), **args)
def Hidden(**args):
return Element(qname = (FORMNS,'hidden'), **args)
def Image(**args):
return Element(qname = (FORMNS,'image'), **args)
def ImageFrame(**args):
return Element(qname = (FORMNS,'image-frame'), **args)
def Item(**args):
return Element(qname = (FORMNS,'item'), **args)
def ListProperty(**args):
return Element(qname = (FORMNS,'list-property'), **args)
def ListValue(**args):
return Element(qname = (FORMNS,'list-value'), **args)
def Listbox(**args):
return Element(qname = (FORMNS,'listbox'), **args)
def Number(**args):
return Element(qname = (FORMNS,'number'), **args)
def Option(**args):
return Element(qname = (FORMNS,'option'), **args)
def Password(**args):
return Element(qname = (FORMNS,'password'), **args)
def Properties(**args):
return Element(qname = (FORMNS,'properties'), **args)
def Property(**args):
return Element(qname = (FORMNS,'property'), **args)
def Radio(**args):
return Element(qname = (FORMNS,'radio'), **args)
def Text(**args):
return Element(qname = (FORMNS,'text'), **args)
def Textarea(**args):
return Element(qname = (FORMNS,'textarea'), **args)
def Time(**args):
return Element(qname = (FORMNS,'time'), **args)
def ValueRange(**args):
return Element(qname = (FORMNS,'value-range'), **args)

8150
src/odf/grammar.py Normal file

File diff suppressed because it is too large Load Diff

117
src/odf/load.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2007-2008 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
# This script is to be embedded in opendocument.py later
# The purpose is to read an ODT/ODP/ODS file and create the datastructure
# in memory. The user should then be able to make operations and then save
# the structure again.
from xml.sax import make_parser,handler
from xml.sax.xmlreader import InputSource
import xml.sax.saxutils
from element import Element
from namespaces import OFFICENS
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
#
# Parse the XML files
#
class LoadParser(handler.ContentHandler):
""" Extract headings from content.xml of an ODT file """
triggers = (
(OFFICENS, 'automatic-styles'), (OFFICENS, 'body'),
(OFFICENS, 'font-face-decls'), (OFFICENS, 'master-styles'),
(OFFICENS, 'meta'), (OFFICENS, 'scripts'),
(OFFICENS, 'settings'), (OFFICENS, 'styles') )
def __init__(self, document):
self.doc = document
self.data = []
self.level = 0
self.parse = False
def characters(self, data):
if self.parse == False:
return
self.data.append(data)
def startElementNS(self, tag, qname, attrs):
if tag in self.triggers:
self.parse = True
if self.doc._parsing != "styles.xml" and tag == (OFFICENS, 'font-face-decls'):
self.parse = False
if self.parse == False:
return
self.level = self.level + 1
# Add any accumulated text content
content = ''.join(self.data).strip()
if len(content) > 0:
self.parent.addText(content)
self.data = []
# Create the element
attrdict = {}
for (att,value) in attrs.items():
attrdict[att] = value
try:
e = Element(qname = tag, qattributes=attrdict, check_grammar=False)
self.curr = e
except AttributeError, v:
print "Error: %s" % v
if tag == (OFFICENS, 'automatic-styles'):
e = self.doc.automaticstyles
elif tag == (OFFICENS, 'body'):
e = self.doc.body
elif tag == (OFFICENS, 'master-styles'):
e = self.doc.masterstyles
elif tag == (OFFICENS, 'meta'):
e = self.doc.meta
elif tag == (OFFICENS,'scripts'):
e = self.doc.scripts
elif tag == (OFFICENS,'settings'):
e = self.doc.settings
elif tag == (OFFICENS,'styles'):
e = self.doc.styles
elif self.doc._parsing == "styles.xml" and tag == (OFFICENS, 'font-face-decls'):
e = self.doc.fontfacedecls
elif hasattr(self,'parent'):
self.parent.addElement(e, check_grammar=False)
self.parent = e
def endElementNS(self, tag, qname):
if self.parse == False:
return
self.level = self.level - 1
str = ''.join(self.data)
if len(str.strip()) > 0:
self.curr.addText(str)
self.data = []
self.curr = self.curr.parentNode
self.parent = self.curr
if tag in self.triggers:
self.parse = False

53
src/odf/manifest.py Normal file
View File

@ -0,0 +1,53 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
#
from namespaces import MANIFESTNS
from element import Element
# Autogenerated
def Manifest(**args):
return Element(qname = (MANIFESTNS,'manifest'), **args)
def FileEntry(**args):
return Element(qname = (MANIFESTNS,'file-entry'), **args)
def EncryptionData(**args):
return Element(qname = (MANIFESTNS,'encryption-data'), **args)
def Algorithm(**args):
return Element(qname = (MANIFESTNS,'algorithm'), **args)
def KeyDerivation(**args):
return Element(qname = (MANIFESTNS,'key-derivation'), **args)
if __name__ == "__main__":
import cStringIO
xml=cStringIO.StringIO()
m = Manifest()
f = FileEntry(mediatype="text/xml", fullpath="content.xml")
m.addElement(f)
m.toXml(0,xml)
print xml.getvalue()

30
src/odf/math.py Normal file
View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import MATHNS
from element import Element
# ODF 1.0 section 12.5
# Mathematical content is represented by MathML 2.0
# Autogenerated
def Math(**args):
return Element(qname = (MATHNS,'math'), **args)

66
src/odf/meta.py Normal file
View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import METANS
from element import Element
# Autogenerated
def AutoReload(**args):
return Element(qname = (METANS,'auto-reload'), **args)
def CreationDate(**args):
return Element(qname = (METANS,'creation-date'), **args)
def DateString(**args):
return Element(qname = (METANS,'date-string'), **args)
def DocumentStatistic(**args):
return Element(qname = (METANS,'document-statistic'), **args)
def EditingCycles(**args):
return Element(qname = (METANS,'editing-cycles'), **args)
def EditingDuration(**args):
return Element(qname = (METANS,'editing-duration'), **args)
def Generator(**args):
return Element(qname = (METANS,'generator'), **args)
def HyperlinkBehaviour(**args):
return Element(qname = (METANS,'hyperlink-behaviour'), **args)
def InitialCreator(**args):
return Element(qname = (METANS,'initial-creator'), **args)
def Keyword(**args):
return Element(qname = (METANS,'keyword'), **args)
def PrintDate(**args):
return Element(qname = (METANS,'print-date'), **args)
def PrintedBy(**args):
return Element(qname = (METANS,'printed-by'), **args)
def Template(**args):
return Element(qname = (METANS,'template'), **args)
def UserDefined(**args):
return Element(qname = (METANS,'user-defined'), **args)

81
src/odf/namespaces.py Normal file
View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
TOOLSVERSION = u"ODFPY/0.8.1dev"
ANIMNS = u"urn:oasis:names:tc:opendocument:xmlns:animation:1.0"
CHARTNS = u"urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
CONFIGNS = u"urn:oasis:names:tc:opendocument:xmlns:config:1.0"
DBNS = u"http://openoffice.org/2004/database"
DCNS = u"http://purl.org/dc/elements/1.1/"
DOMNS = u"http://www.w3.org/2001/xml-events"
DR3DNS = u"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
DRAWNS = u"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
FONS = u"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
FORMNS = u"urn:oasis:names:tc:opendocument:xmlns:form:1.0"
KOFFICENS = u"http://www.koffice.org/2005/"
MANIFESTNS = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
MATHNS = u"http://www.w3.org/1998/Math/MathML"
METANS = u"urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
NUMBERNS = u"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
OFFICENS = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"
OOONS = u"http://openoffice.org/2004/office"
OOOWNS = u"http://openoffice.org/2004/writer"
OOOCNS = u"http://openoffice.org/2004/calc"
PRESENTATIONNS = u"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
SCRIPTNS = u"urn:oasis:names:tc:opendocument:xmlns:script:1.0"
SMILNS = u"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0"
STYLENS = u"urn:oasis:names:tc:opendocument:xmlns:style:1.0"
SVGNS = u"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
TABLENS = u"urn:oasis:names:tc:opendocument:xmlns:table:1.0"
TEXTNS = u"urn:oasis:names:tc:opendocument:xmlns:text:1.0"
XFORMSNS = u"http://www.w3.org/2002/xforms"
XLINKNS = u"http://www.w3.org/1999/xlink"
nsdict = {
ANIMNS: u'anim',
CHARTNS: u'chart',
CONFIGNS: u'config',
DBNS: u'db',
DCNS: u'dc',
DOMNS: u'dom',
DR3DNS: u'dr3d',
DRAWNS: u'draw',
FONS: u'fo',
FORMNS: u'form',
KOFFICENS: u'koffice',
MANIFESTNS: u'manifest',
MATHNS: u'math',
METANS: u'meta',
NUMBERNS: u'number',
OFFICENS: u'office',
OOONS: u'ooo',
OOOWNS: u'ooow',
OOOCNS: u'ooc',
PRESENTATIONNS: u'presentation',
SCRIPTNS: u'script',
SMILNS: u'smil',
STYLENS: u'style',
SVGNS: u'svg',
TABLENS: u'table',
TEXTNS: u'text',
XFORMSNS: u'xforms',
XLINKNS: u'xlink',
}

104
src/odf/number.py Normal file
View File

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import NUMBERNS
from element import Element
from style import StyleElement
# Autogenerated
def AmPm(**args):
return Element(qname = (NUMBERNS,'am-pm'), **args)
def Boolean(**args):
return Element(qname = (NUMBERNS,'boolean'), **args)
def BooleanStyle(**args):
return StyleElement(qname = (NUMBERNS,'boolean-style'), **args)
def CurrencyStyle(**args):
return StyleElement(qname = (NUMBERNS,'currency-style'), **args)
def CurrencySymbol(**args):
return Element(qname = (NUMBERNS,'currency-symbol'), **args)
def DateStyle(**args):
return StyleElement(qname = (NUMBERNS,'date-style'), **args)
def Day(**args):
return Element(qname = (NUMBERNS,'day'), **args)
def DayOfWeek(**args):
return Element(qname = (NUMBERNS,'day-of-week'), **args)
def EmbeddedText(**args):
return Element(qname = (NUMBERNS,'embedded-text'), **args)
def Era(**args):
return Element(qname = (NUMBERNS,'era'), **args)
def Fraction(**args):
return Element(qname = (NUMBERNS,'fraction'), **args)
def Hours(**args):
return Element(qname = (NUMBERNS,'hours'), **args)
def Minutes(**args):
return Element(qname = (NUMBERNS,'minutes'), **args)
def Month(**args):
return Element(qname = (NUMBERNS,'month'), **args)
def Number(**args):
return Element(qname = (NUMBERNS,'number'), **args)
def NumberStyle(**args):
return StyleElement(qname = (NUMBERNS,'number-style'), **args)
def PercentageStyle(**args):
return StyleElement(qname = (NUMBERNS,'percentage-style'), **args)
def Quarter(**args):
return Element(qname = (NUMBERNS,'quarter'), **args)
def ScientificNumber(**args):
return Element(qname = (NUMBERNS,'scientific-number'), **args)
def Seconds(**args):
return Element(qname = (NUMBERNS,'seconds'), **args)
def Text(**args):
return Element(qname = (NUMBERNS,'text'), **args)
def TextContent(**args):
return Element(qname = (NUMBERNS,'text-content'), **args)
def TextStyle(**args):
return StyleElement(qname = (NUMBERNS,'text-style'), **args)
def TimeStyle(**args):
return StyleElement(qname = (NUMBERNS,'time-style'), **args)
def WeekOfYear(**args):
return Element(qname = (NUMBERNS,'week-of-year'), **args)
def Year(**args):
return Element(qname = (NUMBERNS,'year'), **args)

579
src/odf/odf2moinmoin.py Normal file
View File

@ -0,0 +1,579 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# See http://trac.edgewall.org/wiki/WikiFormatting
#
# Contributor(s):
#
import sys, zipfile, xml.dom.minidom
from namespaces import nsdict
from elementtypes import *
IGNORED_TAGS = [
'draw:a'
'draw:g',
'draw:line',
'draw:object-ole',
'office:annotation',
'presentation:notes',
'svg:desc',
] + [ nsdict[item[0]]+":"+item[1] for item in empty_elements]
INLINE_TAGS = [ nsdict[item[0]]+":"+item[1] for item in inline_elements]
class TextProps:
""" Holds properties for a text style. """
def __init__(self):
self.italic = False
self.bold = False
self.fixed = False
self.underlined = False
self.strikethrough = False
self.superscript = False
self.subscript = False
def setItalic(self, value):
if value == "italic":
self.italic = True
elif value == "normal":
self.italic = False
def setBold(self, value):
if value == "bold":
self.bold = True
elif value == "normal":
self.bold = False
def setFixed(self, value):
self.fixed = value
def setUnderlined(self, value):
if value and value != "none":
self.underlined = True
def setStrikethrough(self, value):
if value and value != "none":
self.strikethrough = True
def setPosition(self, value):
if value is None or value == '':
return
posisize = value.split(' ')
textpos = posisize[0]
if textpos.find('%') == -1:
if textpos == "sub":
self.superscript = False
self.subscript = True
elif textpos == "super":
self.superscript = True
self.subscript = False
else:
itextpos = int(textpos[:textpos.find('%')])
if itextpos > 10:
self.superscript = False
self.subscript = True
elif itextpos < -10:
self.superscript = True
self.subscript = False
def __str__(self):
return "[italic=%s, bold=i%s, fixed=%s]" % (str(self.italic),
str(self.bold),
str(self.fixed))
class ParagraphProps:
""" Holds properties of a paragraph style. """
def __init__(self):
self.blockquote = False
self.headingLevel = 0
self.code = False
self.title = False
self.indented = 0
def setIndented(self, value):
self.indented = value
def setHeading(self, level):
self.headingLevel = level
def setTitle(self, value):
self.title = value
def setCode(self, value):
self.code = value
def __str__(self):
return "[bq=%s, h=%d, code=%s]" % (str(self.blockquote),
self.headingLevel,
str(self.code))
class ListProperties:
""" Holds properties for a list style. """
def __init__(self):
self.ordered = False
def setOrdered(self, value):
self.ordered = value
class ODF2MoinMoin(object):
def __init__(self, filepath):
self.footnotes = []
self.footnoteCounter = 0
self.textStyles = {"Standard": TextProps()}
self.paragraphStyles = {"Standard": ParagraphProps()}
self.listStyles = {}
self.fixedFonts = []
self.hasTitle = 0
self.lastsegment = None
# Tags
self.elements = {
'draw:page': self.textToString,
'draw:frame': self.textToString,
'draw:image': self.draw_image,
'draw:text-box': self.textToString,
'text:a': self.text_a,
'text:note': self.text_note,
}
for tag in IGNORED_TAGS:
self.elements[tag] = self.do_nothing
for tag in INLINE_TAGS:
self.elements[tag] = self.inline_markup
self.elements['text:line-break'] = self.text_line_break
self.elements['text:s'] = self.text_s
self.elements['text:tab'] = self.text_tab
self.load(filepath)
def processFontDeclarations(self, fontDecl):
""" Extracts necessary font information from a font-declaration
element.
"""
for fontFace in fontDecl.getElementsByTagName("style:font-face"):
if fontFace.getAttribute("style:font-pitch") == "fixed":
self.fixedFonts.append(fontFace.getAttribute("style:name"))
def extractTextProperties(self, style, parent=None):
""" Extracts text properties from a style element. """
textProps = TextProps()
if parent:
parentProp = self.textStyles.get(parent, None)
if parentProp:
textProp = parentProp
textPropEl = style.getElementsByTagName("style:text-properties")
if not textPropEl: return textProps
textPropEl = textPropEl[0]
textProps.setItalic(textPropEl.getAttribute("fo:font-style"))
textProps.setBold(textPropEl.getAttribute("fo:font-weight"))
textProps.setUnderlined(textPropEl.getAttribute("style:text-underline-style"))
textProps.setStrikethrough(textPropEl.getAttribute("style:text-line-through-style"))
textProps.setPosition(textPropEl.getAttribute("style:text-position"))
if textPropEl.getAttribute("style:font-name") in self.fixedFonts:
textProps.setFixed(True)
return textProps
def extractParagraphProperties(self, style, parent=None):
""" Extracts paragraph properties from a style element. """
paraProps = ParagraphProps()
name = style.getAttribute("style:name")
if name.startswith("Heading_20_"):
level = name[11:]
try:
level = int(level)
paraProps.setHeading(level)
except:
level = 0
if name == "Title":
paraProps.setTitle(True)
paraPropEl = style.getElementsByTagName("style:paragraph-properties")
if paraPropEl:
paraPropEl = paraPropEl[0]
leftMargin = paraPropEl.getAttribute("fo:margin-left")
if leftMargin:
try:
leftMargin = float(leftMargin[:-2])
if leftMargin > 0.01:
paraProps.setIndented(True)
except:
pass
textProps = self.extractTextProperties(style)
if textProps.fixed:
paraProps.setCode(True)
return paraProps
def processStyles(self, styleElements):
""" Runs through "style" elements extracting necessary information.
"""
for style in styleElements:
name = style.getAttribute("style:name")
if name == "Standard": continue
family = style.getAttribute("style:family")
parent = style.getAttribute("style:parent-style-name")
if family == "text":
self.textStyles[name] = self.extractTextProperties(style, parent)
elif family == "paragraph":
self.paragraphStyles[name] = \
self.extractParagraphProperties(style, parent)
self.textStyles[name] = self.extractTextProperties(style, parent)
def processListStyles(self, listStyleElements):
for style in listStyleElements:
name = style.getAttribute("style:name")
prop = ListProperties()
if style.hasChildNodes():
subitems = [el for el in style.childNodes
if el.nodeType == xml.dom.Node.ELEMENT_NODE
and el.tagName == "text:list-level-style-number"]
if len(subitems) > 0:
prop.setOrdered(True)
self.listStyles[name] = prop
def load(self, filepath):
""" Loads an ODT file. """
zip = zipfile.ZipFile(filepath)
styles_doc = xml.dom.minidom.parseString(zip.read("styles.xml"))
fontfacedecls = styles_doc.getElementsByTagName("office:font-face-decls")
if fontfacedecls:
self.processFontDeclarations(fontfacedecls[0])
self.processStyles(styles_doc.getElementsByTagName("style:style"))
self.processListStyles(styles_doc.getElementsByTagName("text:list-style"))
self.content = xml.dom.minidom.parseString(zip.read("content.xml"))
fontfacedecls = self.content.getElementsByTagName("office:font-face-decls")
if fontfacedecls:
self.processFontDeclarations(fontfacedecls[0])
self.processStyles(self.content.getElementsByTagName("style:style"))
self.processListStyles(self.content.getElementsByTagName("text:list-style"))
def compressCodeBlocks(self, text):
""" Removes extra blank lines from code blocks. """
return text
lines = text.split("\n")
buffer = []
numLines = len(lines)
for i in range(numLines):
if (lines[i].strip() or i == numLines-1 or i == 0 or
not ( lines[i-1].startswith(" ")
and lines[i+1].startswith(" ") ) ):
buffer.append("\n" + lines[i])
return ''.join(buffer)
#-----------------------------------
def do_nothing(self, node):
return ''
def draw_image(self, node):
"""
"""
link = node.getAttribute("xlink:href")
if link and link[:2] == './': # Indicates a sub-object, which isn't supported
return "%s\n" % link
if link and link[:9] == 'Pictures/':
link = link[9:]
return "[[Image(%s)]]\n" % link
def text_a(self, node):
text = self.textToString(node)
link = node.getAttribute("xlink:href")
if link.strip() == text.strip():
return "[%s] " % link.strip()
else:
return "[%s %s] " % (link.strip(), text.strip())
def text_line_break(self, node):
return "[[BR]]"
def text_note(self, node):
cite = (node.getElementsByTagName("text:note-citation")[0]
.childNodes[0].nodeValue)
body = (node.getElementsByTagName("text:note-body")[0]
.childNodes[0])
self.footnotes.append((cite, self.textToString(body)))
return "^%s^" % cite
def text_s(self, node):
try:
num = int(node.getAttribute("text:c"))
return " "*num
except:
return " "
def text_tab(self, node):
return " "
def inline_markup(self, node):
text = self.textToString(node)
if not text.strip():
return '' # don't apply styles to white space
styleName = node.getAttribute("text:style-name")
style = self.textStyles.get(styleName, TextProps())
if style.fixed:
return "`" + text + "`"
mark = []
if style:
if style.italic:
mark.append("''")
if style.bold:
mark.append("'''")
if style.underlined:
mark.append("__")
if style.strikethrough:
mark.append("~~")
if style.superscript:
mark.append("^")
if style.subscript:
mark.append(",,")
revmark = mark[:]
revmark.reverse()
return "%s%s%s" % (''.join(mark), text, ''.join(revmark))
#-----------------------------------
def listToString(self, listElement, indent = 0):
self.lastsegment = listElement.tagName
buffer = []
styleName = listElement.getAttribute("text:style-name")
props = self.listStyles.get(styleName, ListProperties())
i = 0
for item in listElement.childNodes:
buffer.append(" "*indent)
i += 1
if props.ordered:
number = str(i)
number = " " + number + ". "
buffer.append(" 1. ")
else:
buffer.append(" * ")
subitems = [el for el in item.childNodes
if el.tagName in ["text:p", "text:h", "text:list"]]
for subitem in subitems:
if subitem.tagName == "text:list":
buffer.append("\n")
buffer.append(self.listToString(subitem, indent+3))
else:
buffer.append(self.paragraphToString(subitem, indent+3))
self.lastsegment = subitem.tagName
self.lastsegment = item.tagName
buffer.append("\n")
return ''.join(buffer)
def tableToString(self, tableElement):
""" MoinMoin uses || to delimit table cells
"""
self.lastsegment = tableElement.tagName
buffer = []
for item in tableElement.childNodes:
self.lastsegment = item.tagName
if item.tagName == "table:table-header-rows":
buffer.append(self.tableToString(item))
if item.tagName == "table:table-row":
buffer.append("\n||")
for cell in item.childNodes:
buffer.append(self.inline_markup(cell))
buffer.append("||")
self.lastsegment = cell.tagName
return ''.join(buffer)
def toString(self):
""" Converts the document to a string.
FIXME: Result from second call differs from first call
"""
body = self.content.getElementsByTagName("office:body")[0]
text = body.childNodes[0]
buffer = []
paragraphs = [el for el in text.childNodes
if el.tagName in ["draw:page", "text:p", "text:h","text:section",
"text:list", "table:table"]]
for paragraph in paragraphs:
if paragraph.tagName == "text:list":
text = self.listToString(paragraph)
elif paragraph.tagName == "text:section":
text = self.textToString(paragraph)
elif paragraph.tagName == "table:table":
text = self.tableToString(paragraph)
else:
text = self.paragraphToString(paragraph)
if text:
buffer.append(text)
if self.footnotes:
buffer.append("----")
for cite, body in self.footnotes:
buffer.append("%s: %s" % (cite, body))
buffer.append("")
return self.compressCodeBlocks('\n'.join(buffer))
def textToString(self, element):
buffer = []
for node in element.childNodes:
if node.nodeType == xml.dom.Node.TEXT_NODE:
buffer.append(node.nodeValue)
elif node.nodeType == xml.dom.Node.ELEMENT_NODE:
tag = node.tagName
if tag in ("draw:text-box", "draw:frame"):
buffer.append(self.textToString(node))
elif tag in ("text:p", "text:h"):
text = self.paragraphToString(node)
if text:
buffer.append(text)
elif tag == "text:list":
buffer.append(self.listToString(node))
else:
method = self.elements.get(tag)
if method:
buffer.append(method(node))
else:
buffer.append(" {" + tag + "} ")
return ''.join(buffer)
def paragraphToString(self, paragraph, indent = 0):
dummyParaProps = ParagraphProps()
style_name = paragraph.getAttribute("text:style-name")
paraProps = self.paragraphStyles.get(style_name, dummyParaProps)
text = self.inline_markup(paragraph)
if paraProps and not paraProps.code:
text = text.strip()
if paragraph.tagName == "text:p" and self.lastsegment == "text:p":
text = "\n" + text
self.lastsegment = paragraph.tagName
if paraProps.title:
self.hasTitle = 1
return "= " + text + " =\n"
outlinelevel = paragraph.getAttribute("text:outline-level")
if outlinelevel:
level = int(outlinelevel)
if self.hasTitle: level += 1
if level >= 1:
return "=" * level + " " + text + " " + "=" * level + "\n"
elif paraProps.code:
return "{{{\n" + text + "\n}}}\n"
if paraProps.indented:
return self.wrapParagraph(text, indent = indent, blockquote = True)
else:
return self.wrapParagraph(text, indent = indent)
def wrapParagraph(self, text, indent = 0, blockquote=False):
counter = 0
buffer = []
LIMIT = 50
if blockquote:
buffer.append(" ")
return ''.join(buffer) + text
# Unused from here
for token in text.split():
if counter > LIMIT - indent:
buffer.append("\n" + " "*indent)
if blockquote:
buffer.append(" ")
counter = 0
buffer.append(token + " ")
counter += len(token)
return ''.join(buffer)

1264
src/odf/odf2xhtml.py Normal file

File diff suppressed because it is too large Load Diff

120
src/odf/odfmanifest.py Normal file
View File

@ -0,0 +1,120 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
# This script lists the content of the manifest.xml file
import zipfile
from xml.sax import make_parser,handler
from xml.sax.xmlreader import InputSource
import xml.sax.saxutils
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
MANIFESTNS="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
#-----------------------------------------------------------------------------
#
# ODFMANIFESTHANDLER
#
#-----------------------------------------------------------------------------
class ODFManifestHandler(handler.ContentHandler):
""" The ODFManifestHandler parses a manifest file and produces a list of
content """
def __init__(self):
self.manifest = {}
# Tags
# FIXME: Also handle encryption data
self.elements = {
(MANIFESTNS, 'file-entry'): (self.s_file_entry, self.donothing),
}
def handle_starttag(self, tag, method, attrs):
method(tag,attrs)
def handle_endtag(self, tag, method):
method(tag)
def startElementNS(self, tag, qname, attrs):
method = self.elements.get(tag, (None, None))[0]
if method:
self.handle_starttag(tag, method, attrs)
else:
self.unknown_starttag(tag,attrs)
def endElementNS(self, tag, qname):
method = self.elements.get(tag, (None, None))[1]
if method:
self.handle_endtag(tag, method)
else:
self.unknown_endtag(tag)
def unknown_starttag(self, tag, attrs):
pass
def unknown_endtag(self, tag):
pass
def donothing(self, tag, attrs=None):
pass
def s_file_entry(self, tag, attrs):
m = attrs.get((MANIFESTNS, 'media-type'),"application/octet-stream")
p = attrs.get((MANIFESTNS, 'full-path'))
self.manifest[p] = { 'media-type':m, 'full-path':p }
#-----------------------------------------------------------------------------
#
# Reading the file
#
#-----------------------------------------------------------------------------
def manifestlist(manifestxml):
odhandler = ODFManifestHandler()
parser = make_parser()
parser.setFeature(handler.feature_namespaces, 1)
parser.setContentHandler(odhandler)
parser.setErrorHandler(handler.ErrorHandler())
inpsrc = InputSource()
inpsrc.setByteStream(StringIO(manifestxml))
parser.parse(inpsrc)
return odhandler.manifest
def odfmanifest(odtfile):
z = zipfile.ZipFile(odtfile)
manifest = z.read('META-INF/manifest.xml')
z.close()
return manifestlist(manifest)
if __name__ == "__main__":
import sys
result = odfmanifest(sys.argv[1])
for file in result.values():
print "%-40s %-40s" % (file['media-type'], file['full-path'])

104
src/odf/office.py Normal file
View File

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import OFFICENS
from element import Element
from draw import StyleRefElement
# Autogenerated
def Annotation(**args):
return StyleRefElement(qname = (OFFICENS,'annotation'), **args)
def AutomaticStyles(**args):
return Element(qname = (OFFICENS, 'automatic-styles'), **args)
def BinaryData(**args):
return Element(qname = (OFFICENS,'binary-data'), **args)
def Body(**args):
return Element(qname = (OFFICENS, 'body'), **args)
def ChangeInfo(**args):
return Element(qname = (OFFICENS,'change-info'), **args)
def Chart(**args):
return Element(qname = (OFFICENS,'chart'), **args)
def DdeSource(**args):
return Element(qname = (OFFICENS,'dde-source'), **args)
def Document(version="1.0", **args):
return Element(qname = (OFFICENS,'document'), version=version, **args)
def DocumentContent(version="1.0", **args):
return Element(qname = (OFFICENS, 'document-content'), version=version, **args)
def DocumentMeta(version="1.0", **args):
return Element(qname = (OFFICENS, 'document-meta'), version=version, **args)
def DocumentSettings(version="1.0", **args):
return Element(qname = (OFFICENS, 'document-settings'), version=version, **args)
def DocumentStyles(version="1.0", **args):
return Element(qname = (OFFICENS, 'document-styles'), version=version, **args)
def Drawing(**args):
return Element(qname = (OFFICENS,'drawing'), **args)
def EventListeners(**args):
return Element(qname = (OFFICENS,'event-listeners'), **args)
def FontFaceDecls(**args):
return Element(qname = (OFFICENS, 'font-face-decls'), **args)
def Forms(**args):
return Element(qname = (OFFICENS,'forms'), **args)
def Image(**args):
return Element(qname = (OFFICENS,'image'), **args)
def MasterStyles(**args):
return Element(qname = (OFFICENS, 'master-styles'), **args)
def Meta(**args):
return Element(qname = (OFFICENS, 'meta'), **args)
def Presentation(**args):
return Element(qname = (OFFICENS,'presentation'), **args)
def Script(**args):
return Element(qname = (OFFICENS, 'script'), **args)
def Scripts(**args):
return Element(qname = (OFFICENS, 'scripts'), **args)
def Settings(**args):
return Element(qname = (OFFICENS, 'settings'), **args)
def Spreadsheet(**args):
return Element(qname = (OFFICENS, 'spreadsheet'), **args)
def Styles(**args):
return Element(qname = (OFFICENS, 'styles'), **args)
def Text(**args):
return Element(qname = (OFFICENS, 'text'), **args)
# Autogenerated end

71
src/odf/ooostyles.py Normal file
View File

@ -0,0 +1,71 @@
from style import Style, ParagraphProperties, TextProperties
def addOOoStandardStyles(styles):
style = Style(name="Standard", family="paragraph", attributes={'class':"text"})
styles.addElement(style)
style = Style(name="Text_20_body", displayname="Text body", family="paragraph", parentstylename="Standard", attributes={'class':"text"})
p = ParagraphProperties(margintop="0cm", marginbottom="0.212cm")
style.addElement(p)
styles.addElement(style)
style = Style(name="Text_20_body_20_indent", displayname="Text body indent", family="paragraph", parentstylename="Text_20_body", attributes={'class':"text"})
p = ParagraphProperties(marginleft="0.499cm", marginright="0cm", textindent="0cm", autotextindent="false")
style.addElement(p)
styles.addElement(style)
style = Style(name="Salutation", family="paragraph", parentstylename="Standard", attributes={'class':"text"})
p = ParagraphProperties(numberlines="false", linenumber=0)
style.addElement(p)
styles.addElement(style)
style = Style(name="Signature", family="paragraph", parentstylename="Standard", attributes={'class':"text"})
p = ParagraphProperties(numberlines="false", linenumber=0)
style.addElement(p)
styles.addElement(style)
style = Style(name="Heading", family="paragraph", parentstylename="Standard", nextstylename="Text_20_body", attributes={'class':"text"})
p = ParagraphProperties(margintop="0.423cm", marginbottom="0.212cm", keepwithnext="always")
style.addElement(p)
p = TextProperties(fontname="Nimbus Sans L", fontsize="14pt", fontnameasian="DejaVu LGC Sans", fontsizeasian="14pt", fontnamecomplex="DejaVu LGC Sans", fontsizecomplex="14pt")
style.addElement(p)
styles.addElement(style)
style = Style(name="Heading_20_1", displayname="Heading 1", family="paragraph", parentstylename="Heading", nextstylename="Text_20_body", attributes={'class':"text"}, defaultoutlinelevel=1)
p = TextProperties(fontsize="115%", fontweight="bold", fontsizeasian="115%", fontweightasian="bold", fontsizecomplex="115%", fontweightcomplex="bold")
style.addElement(p)
styles.addElement(style)
style = Style(name="Heading_20_2", displayname="Heading 2", family="paragraph", parentstylename="Heading", nextstylename="Text_20_body", attributes={'class':"text"}, defaultoutlinelevel=2)
p = TextProperties(fontsize="14pt", fontstyle="italic", fontweight="bold", fontsizeasian="14pt", fontstyleasian="italic", fontweightasian="bold", fontsizecomplex="14pt", fontstylecomplex="italic", fontweightcomplex="bold")
style.addElement(p)
styles.addElement(style)
style = Style(name="Heading_20_3", displayname="Heading 3", family="paragraph", parentstylename="Heading", nextstylename="Text_20_body", attributes={'class':"text"}, defaultoutlinelevel=3)
p = TextProperties(fontsize="14pt", fontweight="bold", fontsizeasian="14pt", fontweightasian="bold", fontsizecomplex="14pt", fontweightcomplex="bold")
style.addElement(p)
styles.addElement(style)
style = Style(name="List", family="paragraph", parentstylename="Text_20_body", attributes={'class':"list"})
styles.addElement(style)
style = Style(name="Caption", family="paragraph", parentstylename="Standard", attributes={'class':"extra"})
p = ParagraphProperties(margintop="0.212cm", marginbottom="0.212cm", numberlines="false", linenumber="0")
style.addElement(p)
p = TextProperties(fontsize="12pt", fontstyle="italic", fontsizeasian="12pt", fontstyleasian="italic", fontsizecomplex="12pt", fontstylecomplex="italic")
style.addElement(p)
styles.addElement(style)
style = Style(name="Index", family="paragraph", parentstylename="Standard", attributes={'class':"index"})
p = ParagraphProperties(numberlines="false", linenumber=0)
styles.addElement(style)
style = Style(name="Source_20_Text", displayname="Source Text", family="text")
p = TextProperties(fontname="Courier", fontnameasian="Courier", fontnamecomplex="Courier")
style.addElement(p)
styles.addElement(style)
style = Style(name="Variable", family="text")
p = TextProperties(fontstyle="italic", fontstyleasian="italic", fontstylecomplex="italic")
style.addElement(p)
styles.addElement(style)

558
src/odf/opendocument.py Normal file
View File

@ -0,0 +1,558 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2008 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
__doc__="""Use OpenDocument to generate your documents."""
import zipfile, time, sys, mimetypes, copy
from cStringIO import StringIO
from namespaces import *
import manifest, meta
from office import *
import element
from attrconverters import make_NCName
from xml.sax.xmlreader import InputSource
from odfmanifest import manifestlist
__version__= TOOLSVERSION
_XMLPROLOGUE = u"<?xml version='1.0' encoding='UTF-8'?>\n"
UNIXPERMS = 0100644 << 16L # -rw-r--r--
IS_FILENAME = 0
IS_IMAGE = 1
# We need at least Python 2.2
assert sys.version_info[0]>=2 and sys.version_info[1] >= 2
sys.setrecursionlimit=50
#The recursion limit is set conservative so mistakes like
# s=content() s.addElement(s) won't eat up too much processor time.
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',
}
class OpenDocument:
""" A class to hold the content of an OpenDocument document
Use the xml method to write the XML
source to the screen or to a file
d = OpenDocument(mimetype)
fd.write(d.xml())
"""
thumbnail = None
def __init__(self, mimetype, add_generator=True):
self.mimetype = mimetype
self.childobjects = []
self.folder = "" # Always empty for toplevel documents
self.topnode = Document(mimetype=self.mimetype)
self.topnode.ownerDocument = self
self.clear_caches()
self.Pictures = {}
self.meta = Meta()
self.topnode.addElement(self.meta)
if add_generator:
self.meta.addElement(meta.Generator(text=TOOLSVERSION))
self.scripts = Scripts()
self.topnode.addElement(self.scripts)
self.fontfacedecls = FontFaceDecls()
self.topnode.addElement(self.fontfacedecls)
self.settings = Settings()
self.topnode.addElement(self.settings)
self.styles = Styles()
self.topnode.addElement(self.styles)
self.automaticstyles = AutomaticStyles()
self.topnode.addElement(self.automaticstyles)
self.masterstyles = MasterStyles()
self.topnode.addElement(self.masterstyles)
self.body = Body()
self.topnode.addElement(self.body)
def rebuild_caches(self, node=None):
if node is None: node = self.topnode
self.build_caches(node)
for e in node.childNodes:
if e.nodeType == element.Node.ELEMENT_NODE:
self.rebuild_caches(e)
def clear_caches(self):
self.element_dict = {}
self._styles_dict = {}
self._styles_ooo_fix = {}
def build_caches(self, element):
""" Called from element.py
"""
if not self.element_dict.has_key(element.qname):
self.element_dict[element.qname] = []
self.element_dict[element.qname].append(element)
if element.qname == (STYLENS, u'style'):
self._register_stylename(element) # Add to style dictionary
styleref = element.getAttrNS(TEXTNS,u'style-name')
if styleref is not None and self._styles_ooo_fix.has_key(styleref):
element.setAttrNS(TEXTNS,u'style-name', self._styles_ooo_fix[styleref])
def _register_stylename(self, element):
''' Register a style. But there are three style dictionaries:
office:styles, office:automatic-styles and office:master-styles
Chapter 14
'''
name = element.getAttrNS(STYLENS, u'name')
if name is None:
return
if element.parentNode.qname in ((OFFICENS,u'styles'), (OFFICENS,u'automatic-styles')):
if self._styles_dict.has_key(name):
newname = 'M'+name # Rename style
self._styles_ooo_fix[name] = newname
# From here on all references to the old name will refer to the new one
name = newname
element.setAttrNS(STYLENS, u'name', name)
self._styles_dict[name] = element
def toXml(self, filename=''):
xml=StringIO()
xml.write(_XMLPROLOGUE)
self.body.toXml(0, xml)
if not filename:
return xml.getvalue()
else:
f=file(filename,'w')
f.write(xml.getvalue())
f.close()
def xml(self):
""" Generates the full document as an XML file
Always written as a bytestream in UTF-8 encoding
"""
self._replaceGenerator()
xml=StringIO()
xml.write(_XMLPROLOGUE)
self.topnode.toXml(0, xml)
return xml.getvalue()
def contentxml(self):
""" Generates the content.xml file
Always written as a bytestream in UTF-8 encoding
"""
xml=StringIO()
xml.write(_XMLPROLOGUE)
x = DocumentContent()
x.write_open_tag(0, xml)
if self.scripts.hasChildNodes():
self.scripts.toXml(1, xml)
if self.fontfacedecls.hasChildNodes():
self.fontfacedecls.toXml(1, xml)
a = AutomaticStyles()
stylelist = self._used_auto_styles([self.styles, self.body])
if len(stylelist) > 0:
a.write_open_tag(1, xml)
for s in stylelist:
s.toXml(2, xml)
a.write_close_tag(1, xml)
else:
a.toXml(1, xml)
self.body.toXml(1, xml)
x.write_close_tag(0, xml)
return xml.getvalue()
def manifestxml(self):
""" Generates the manifest.xml file """
xml=StringIO()
xml.write(_XMLPROLOGUE)
self.manifest.toXml(0,xml)
return xml.getvalue()
def metaxml(self):
""" Generates the meta.xml file """
self._replaceGenerator()
x = DocumentMeta()
x.addElement(self.meta)
xml=StringIO()
xml.write(_XMLPROLOGUE)
x.toXml(0,xml)
return xml.getvalue()
def settingsxml(self):
""" Generates the settings.xml file """
x = DocumentSettings()
x.addElement(self.settings)
xml=StringIO()
xml.write(_XMLPROLOGUE)
x.toXml(0,xml)
return xml.getvalue()
def _parseoneelement(self, top, stylenamelist):
""" Finds references to style objects in master-styles
and add the style name to the style list if not already there.
Recursive
"""
for e in top.childNodes:
if e.nodeType == element.Node.ELEMENT_NODE:
for styleref in ( (DRAWNS,u'style-name'),
(DRAWNS,u'text-style-name'),
(PRESENTATIONNS,u'style-name'),
(STYLENS,u'style-name'),
(STYLENS,u'list-style-name'),
(STYLENS,u'page-layout-name'),
(TABLENS,u'style-name'),
(TEXTNS,u'style-name') ):
if e.getAttrNS(styleref[0],styleref[1]):
stylename = e.getAttrNS(styleref[0],styleref[1])
if stylename not in stylenamelist:
stylenamelist.append(stylename)
stylenamelist = self._parseoneelement(e, stylenamelist)
return stylenamelist
def _used_auto_styles(self, segments):
""" Loop through the masterstyles elements, and find the automatic
styles that are used. These will be added to the automatic-styles
element in styles.xml
"""
stylenamelist = []
for top in segments:
stylenamelist = self._parseoneelement(top, stylenamelist)
stylelist = []
for e in self.automaticstyles.childNodes:
if e.getAttrNS(STYLENS,u'name') in stylenamelist:
stylelist.append(e)
return stylelist
def stylesxml(self):
""" Generates the styles.xml file """
xml=StringIO()
xml.write(_XMLPROLOGUE)
x = DocumentStyles()
x.write_open_tag(0, xml)
if self.fontfacedecls.hasChildNodes():
self.fontfacedecls.toXml(1, xml)
self.styles.toXml(1, xml)
a = AutomaticStyles()
a.write_open_tag(1, xml)
for s in self._used_auto_styles([self.masterstyles]):
s.toXml(2, xml)
a.write_close_tag(1, xml)
if self.masterstyles.hasChildNodes():
self.masterstyles.toXml(1, xml)
x.write_close_tag(0, xml)
return xml.getvalue()
def addPicture(self, filename, mediatype=None, content=None):
""" Add a picture
It uses the same convention as OOo, in that it saves the picture in
the zipfile in the subdirectory 'Pictures'
If passed a file ptr, mediatype must be set
"""
if content is None:
if mediatype is None:
mediatype, encoding = mimetypes.guess_type(filename)
if mediatype is None:
mediatype = ''
try: ext = filename[filename.rindex('.'):]
except: ext=''
else:
ext = mimetypes.guess_extension(mediatype)
manifestfn = "Pictures/%0.0f%s" % ((time.time()*10000000000), ext)
self.Pictures[manifestfn] = (IS_FILENAME, fileobj, mediatype)
else:
manifestfn = filename
self.Pictures[manifestfn] = (IS_IMAGE, content, mediatype)
return manifestfn
def addThumbnail(self, filecontent=None):
""" Add a fixed thumbnail
The thumbnail in the library is big, so this is pretty useless.
"""
if filecontent is None:
import thumbnail
self.thumbnail = thumbnail.thumbnail()
else:
self.thumbnail = filecontent
def addObject(self, document):
""" Add an object. The object must be an OpenDocument class
The return value will be the folder in the zipfile the object is stored in
"""
self.childobjects.append(document)
document.folder = "%s/Object %d" % (self.folder, len(self.childobjects))
return ".%s" % document.folder
def _savePictures(self, object, folder):
hasPictures = False
for arcname, picturerec in object.Pictures.items():
what_it_is, fileobj, mediatype = picturerec
self.manifest.addElement(manifest.FileEntry(fullpath="%s%s" % ( folder ,arcname), mediatype=mediatype))
hasPictures = True
if what_it_is == IS_FILENAME:
self._z.write(fileobj, arcname, zipfile.ZIP_STORED)
else:
zi = zipfile.ZipInfo(str(arcname), self._now)
zi.compress_type = zipfile.ZIP_STORED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, fileobj)
if hasPictures:
self.manifest.addElement(manifest.FileEntry(fullpath="%sPictures/" % folder,mediatype=""))
# Look in subobjects
subobjectnum = 1
for subobject in object.childobjects:
self._savePictures(subobject,'%sObject %d/' % (folder, subobjectnum))
subobjectnum += 1
def _replaceGenerator(self):
""" Section 3.1.1: The application MUST NOT export the original identifier
belonging to the application that created the document.
"""
for m in self.meta.childNodes[:]:
if m.qname == (METANS, u'generator'):
self.meta.removeChild(m)
self.meta.addElement(meta.Generator(text=TOOLSVERSION))
def save(self, outputfile, addsuffix=False):
""" Save the document under the filename """
if outputfile == '-':
outputfp = zipfile.ZipFile(sys.stdout,"w")
else:
if addsuffix:
outputfile = outputfile + odmimetypes.get(self.mimetype,'.xxx')
outputfp = zipfile.ZipFile(outputfile,"w")
self._zipwrite(outputfp)
outputfp.close()
def write(self, outputfp):
zipoutputfp = zipfile.ZipFile(outputfp,"w")
self._zipwrite(zipoutputfp)
def _zipwrite(self, outputfp):
""" Write the document to an open file pointer """
self._z = outputfp
self._now = time.localtime()[:6]
self.manifest = manifest.Manifest()
# Write mimetype
zi = zipfile.ZipInfo('mimetype', self._now)
zi.compress_type = zipfile.ZIP_STORED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, self.mimetype)
self._saveXmlObjects(self,"")
# Write pictures
self._savePictures(self,"")
# Write the thumbnail
if self.thumbnail is not None:
self.manifest.addElement(manifest.FileEntry(fullpath="Thumbnails/", mediatype=''))
self.manifest.addElement(manifest.FileEntry(fullpath="Thumbnails/thumbnail.png", mediatype=''))
zi = zipfile.ZipInfo("Thumbnails/thumbnail.png", self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, self.thumbnail)
# Write manifest
zi = zipfile.ZipInfo("META-INF/manifest.xml", self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, self.manifestxml() )
del self._z
del self._now
del self.manifest
def _saveXmlObjects(self, object, folder):
if self == object:
self.manifest.addElement(manifest.FileEntry(fullpath="/", mediatype=object.mimetype))
else:
self.manifest.addElement(manifest.FileEntry(fullpath=folder, mediatype=object.mimetype))
# Write styles
self.manifest.addElement(manifest.FileEntry(fullpath="%sstyles.xml" % folder, mediatype="text/xml"))
zi = zipfile.ZipInfo("%sstyles.xml" % folder, self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, object.stylesxml() )
# Write content
self.manifest.addElement(manifest.FileEntry(fullpath="%scontent.xml" % folder, mediatype="text/xml"))
zi = zipfile.ZipInfo("%scontent.xml" % folder, self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, object.contentxml() )
# Write settings
if self == object and self.settings.hasChildNodes():
self.manifest.addElement(manifest.FileEntry(fullpath="settings.xml",mediatype="text/xml"))
zi = zipfile.ZipInfo("%ssettings.xml" % folder, self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, object.settingsxml() )
# Write meta
if self == object:
self.manifest.addElement(manifest.FileEntry(fullpath="meta.xml",mediatype="text/xml"))
zi = zipfile.ZipInfo("meta.xml", self._now)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = UNIXPERMS
self._z.writestr(zi, object.metaxml() )
# Write subobjects
subobjectnum = 1
for subobject in object.childobjects:
self._saveXmlObjects(subobject, '%sObject %d/' % (folder, subobjectnum))
subobjectnum += 1
# Document's DOM methods
def createElement(self, element):
""" Inconvenient interface to create an element, but follows XML-DOM.
Does not allow attributes as argument, therefore can't check grammar.
"""
return element(check_grammar=False)
def createTextNode(self, data):
""" Method to create a text node """
return element.Text(data)
def createCDATASection(self, data):
return element.CDATASection(cdata)
def getMediaType(self):
""" Returns the media type """
return self.mimetype
def getStyleByName(self, name):
ncname = make_NCName(name)
if self._styles_dict == {}:
self.rebuild_caches()
return self._styles_dict.get(ncname, None)
def getElementsByType(self, element):
obj = element(check_grammar=False)
if self.element_dict == {}:
self.rebuild_caches()
return self.element_dict.get(obj.qname, [])
# Convenience functions
def OpenDocumentChart():
doc = OpenDocument('application/vnd.oasis.opendocument.chart')
doc.chart = Chart()
doc.body.addElement(doc.chart)
return doc
def OpenDocumentDrawing():
doc = OpenDocument('application/vnd.oasis.opendocument.graphics')
doc.drawing = Drawing()
doc.body.addElement(doc.drawing)
return doc
def OpenDocumentImage():
doc = OpenDocument('application/vnd.oasis.opendocument.image')
doc.image = Image()
doc.body.addElement(doc.image)
return doc
def OpenDocumentPresentation():
doc = OpenDocument('application/vnd.oasis.opendocument.presentation')
doc.presentation = Presentation()
doc.body.addElement(doc.presentation)
return doc
def OpenDocumentSpreadsheet():
doc = OpenDocument('application/vnd.oasis.opendocument.spreadsheet')
doc.spreadsheet = Spreadsheet()
doc.body.addElement(doc.spreadsheet)
return doc
def OpenDocumentText():
doc = OpenDocument('application/vnd.oasis.opendocument.text')
doc.text = Text()
doc.body.addElement(doc.text)
return doc
def load(odffile):
from load import LoadParser
from xml.sax import make_parser, handler
z = zipfile.ZipFile(odffile)
mimetype = z.read('mimetype')
doc = OpenDocument(mimetype, add_generator=False)
# Look in the manifest file to see if which of the four files there are
manifestpart = z.read('META-INF/manifest.xml')
manifest = manifestlist(manifestpart)
for xmlfile in ('settings.xml', 'meta.xml', 'content.xml', 'styles.xml'):
if not manifest.has_key(xmlfile):
continue
try:
xmlpart = z.read(xmlfile)
doc._parsing = xmlfile
parser = make_parser()
parser.setFeature(handler.feature_namespaces, 1)
parser.setContentHandler(LoadParser(doc))
parser.setErrorHandler(handler.ErrorHandler())
inpsrc = InputSource()
inpsrc.setByteStream(StringIO(xmlpart))
parser.parse(inpsrc)
del doc._parsing
except KeyError, v: pass
# Add the thumbnail here
# Add the images here
for mentry,mvalue in manifest.items():
if mentry[:9] == "Pictures/" and len(mentry) > 9:
doc.addPicture(mvalue['full-path'], mvalue['media-type'], z.read(mentry))
elif mentry == "Thumbnails/thumbnail.png":
doc.addThumbnail(z.read(mentry))
else:
pass # Add the SUN junk here to the struct somewhere
# It is cached data, so it can be out-of-date
z.close()
b = doc.getElementsByType(Body)
if mimetype[:39] == 'application/vnd.oasis.opendocument.text':
doc.text = b[0].firstChild
elif mimetype[:43] == 'application/vnd.oasis.opendocument.graphics':
doc.graphics = b[0].firstChild
elif mimetype[:47] == 'application/vnd.oasis.opendocument.presentation':
doc.presentation = b[0].firstChild
elif mimetype[:46] == 'application/vnd.oasis.opendocument.spreadsheet':
doc.spreadsheet = b[0].firstChild
elif mimetype[:40] == 'application/vnd.oasis.opendocument.chart':
doc.chart = b[0].firstChild
elif mimetype[:40] == 'application/vnd.oasis.opendocument.image':
doc.image = b[0].firstChild
elif mimetype[:42] == 'application/vnd.oasis.opendocument.formula':
doc.formula = b[0].firstChild
return doc
# vim: set expandtab sw=4 :

85
src/odf/presentation.py Normal file
View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import PRESENTATIONNS
from element import Element
# ODF 1.0 section 9.6 and 9.7
# Autogenerated
def AnimationGroup(**args):
return Element(qname = (PRESENTATIONNS,'animation-group'), **args)
def Animations(**args):
return Element(qname = (PRESENTATIONNS,'animations'), **args)
def DateTime(**args):
return Element(qname = (PRESENTATIONNS,'date-time'), **args)
def DateTimeDecl(**args):
return Element(qname = (PRESENTATIONNS,'date-time-decl'), **args)
def Dim(**args):
return Element(qname = (PRESENTATIONNS,'dim'), **args)
def EventListener(**args):
return Element(qname = (PRESENTATIONNS,'event-listener'), **args)
def Footer(**args):
return Element(qname = (PRESENTATIONNS,'footer'), **args)
def FooterDecl(**args):
return Element(qname = (PRESENTATIONNS,'footer-decl'), **args)
def Header(**args):
return Element(qname = (PRESENTATIONNS,'header'), **args)
def HeaderDecl(**args):
return Element(qname = (PRESENTATIONNS,'header-decl'), **args)
def HideShape(**args):
return Element(qname = (PRESENTATIONNS,'hide-shape'), **args)
def HideText(**args):
return Element(qname = (PRESENTATIONNS,'hide-text'), **args)
def Notes(**args):
return Element(qname = (PRESENTATIONNS,'notes'), **args)
def Placeholder(**args):
return Element(qname = (PRESENTATIONNS,'placeholder'), **args)
def Play(**args):
return Element(qname = (PRESENTATIONNS,'play'), **args)
def Settings(**args):
return Element(qname = (PRESENTATIONNS,'settings'), **args)
def Show(**args):
return Element(qname = (PRESENTATIONNS,'show'), **args)
def ShowShape(**args):
return Element(qname = (PRESENTATIONNS,'show-shape'), **args)
def ShowText(**args):
return Element(qname = (PRESENTATIONNS,'show-text'), **args)
def Sound(**args):
return Element(qname = (PRESENTATIONNS,'sound'), **args)

30
src/odf/script.py Normal file
View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import SCRIPTNS
from element import Element
# ODF 1.0 section 12.4.1
# The <script:event-listener> element binds an event to a macro.
# Autogenerated
def EventListener(**args):
return Element(qname = (SCRIPTNS,'event-listener'), **args)

148
src/odf/style.py Normal file
View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import STYLENS
from element import Element
def StyleElement(**args):
e = Element(**args)
if args.get('check_grammar', True) == True:
if not args.has_key('displayname'):
e.setAttrNS(STYLENS,'display-name', args.get('name'))
return e
# Autogenerated
def BackgroundImage(**args):
return Element(qname = (STYLENS,'background-image'), **args)
def ChartProperties(**args):
return Element(qname = (STYLENS,'chart-properties'), **args)
def Column(**args):
return Element(qname = (STYLENS,'column'), **args)
def ColumnSep(**args):
return Element(qname = (STYLENS,'column-sep'), **args)
def Columns(**args):
return Element(qname = (STYLENS,'columns'), **args)
def DefaultStyle(**args):
return Element(qname = (STYLENS,'default-style'), **args)
def DrawingPageProperties(**args):
return Element(qname = (STYLENS,'drawing-page-properties'), **args)
def DropCap(**args):
return Element(qname = (STYLENS,'drop-cap'), **args)
def FontFace(**args):
return Element(qname = (STYLENS,'font-face'), **args)
def Footer(**args):
return Element(qname = (STYLENS,'footer'), **args)
def FooterLeft(**args):
return Element(qname = (STYLENS,'footer-left'), **args)
def FooterStyle(**args):
return Element(qname = (STYLENS,'footer-style'), **args)
def FootnoteSep(**args):
return Element(qname = (STYLENS,'footnote-sep'), **args)
def GraphicProperties(**args):
return Element(qname = (STYLENS,'graphic-properties'), **args)
def HandoutMaster(**args):
return Element(qname = (STYLENS,'handout-master'), **args)
def Header(**args):
return Element(qname = (STYLENS,'header'), **args)
def HeaderFooterProperties(**args):
return Element(qname = (STYLENS,'header-footer-properties'), **args)
def HeaderLeft(**args):
return Element(qname = (STYLENS,'header-left'), **args)
def HeaderStyle(**args):
return Element(qname = (STYLENS,'header-style'), **args)
def ListLevelProperties(**args):
return Element(qname = (STYLENS,'list-level-properties'), **args)
def Map(**args):
return Element(qname = (STYLENS,'map'), **args)
def MasterPage(**args):
return StyleElement(qname = (STYLENS,'master-page'), **args)
def PageLayout(**args):
return Element(qname = (STYLENS,'page-layout'), **args)
def PageLayoutProperties(**args):
return Element(qname = (STYLENS,'page-layout-properties'), **args)
def ParagraphProperties(**args):
return Element(qname = (STYLENS,'paragraph-properties'), **args)
def PresentationPageLayout(**args):
return StyleElement(qname = (STYLENS,'presentation-page-layout'), **args)
def RegionCenter(**args):
return Element(qname = (STYLENS,'region-center'), **args)
def RegionLeft(**args):
return Element(qname = (STYLENS,'region-left'), **args)
def RegionRight(**args):
return Element(qname = (STYLENS,'region-right'), **args)
def RubyProperties(**args):
return Element(qname = (STYLENS,'ruby-properties'), **args)
def SectionProperties(**args):
return Element(qname = (STYLENS,'section-properties'), **args)
def Style(**args):
return StyleElement(qname = (STYLENS,'style'), **args)
def TabStop(**args):
return Element(qname = (STYLENS,'tab-stop'), **args)
def TabStops(**args):
return Element(qname = (STYLENS,'tab-stops'), **args)
def TableCellProperties(**args):
return Element(qname = (STYLENS,'table-cell-properties'), **args)
def TableColumnProperties(**args):
return Element(qname = (STYLENS,'table-column-properties'), **args)
def TableProperties(**args):
return Element(qname = (STYLENS,'table-properties'), **args)
def TableRowProperties(**args):
return Element(qname = (STYLENS,'table-row-properties'), **args)
def TextProperties(**args):
return Element(qname = (STYLENS,'text-properties'), **args)

52
src/odf/svg.py Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import SVGNS
from element import Element
from draw import DrawElement
# Autogenerated
def DefinitionSrc(**args):
return Element(qname = (SVGNS,'definition-src'), **args)
def Desc(**args):
return Element(qname = (SVGNS,'desc'), **args)
def FontFaceFormat(**args):
return Element(qname = (SVGNS,'font-face-format'), **args)
def FontFaceName(**args):
return Element(qname = (SVGNS,'font-face-name'), **args)
def FontFaceSrc(**args):
return Element(qname = (SVGNS,'font-face-src'), **args)
def FontFaceUri(**args):
return Element(qname = (SVGNS,'font-face-uri'), **args)
def Lineargradient(**args):
return DrawElement(qname = (SVGNS,'linearGradient'), **args)
def Radialgradient(**args):
return DrawElement(qname = (SVGNS,'radialGradient'), **args)
def Stop(**args):
return Element(qname = (SVGNS,'stop'), **args)

307
src/odf/table.py Normal file
View File

@ -0,0 +1,307 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import TABLENS
from element import Element
# Autogenerated
def Body(**args):
return Element(qname = (TABLENS,'body'), **args)
def CalculationSettings(**args):
return Element(qname = (TABLENS,'calculation-settings'), **args)
def CellAddress(**args):
return Element(qname = (TABLENS,'cell-address'), **args)
def CellContentChange(**args):
return Element(qname = (TABLENS,'cell-content-change'), **args)
def CellContentDeletion(**args):
return Element(qname = (TABLENS,'cell-content-deletion'), **args)
def CellRangeSource(**args):
return Element(qname = (TABLENS,'cell-range-source'), **args)
def ChangeDeletion(**args):
return Element(qname = (TABLENS,'change-deletion'), **args)
def ChangeTrackTableCell(**args):
return Element(qname = (TABLENS,'change-track-table-cell'), **args)
def Consolidation(**args):
return Element(qname = (TABLENS,'consolidation'), **args)
def ContentValidation(**args):
return Element(qname = (TABLENS,'content-validation'), **args)
def ContentValidations(**args):
return Element(qname = (TABLENS,'content-validations'), **args)
def CoveredTableCell(**args):
return Element(qname = (TABLENS,'covered-table-cell'), **args)
def CutOffs(**args):
return Element(qname = (TABLENS,'cut-offs'), **args)
def DataPilotDisplayInfo(**args):
return Element(qname = (TABLENS,'data-pilot-display-info'), **args)
def DataPilotField(**args):
return Element(qname = (TABLENS,'data-pilot-field'), **args)
def DataPilotFieldReference(**args):
return Element(qname = (TABLENS,'data-pilot-field-reference'), **args)
def DataPilotGroup(**args):
return Element(qname = (TABLENS,'data-pilot-group'), **args)
def DataPilotGroupMember(**args):
return Element(qname = (TABLENS,'data-pilot-group-member'), **args)
def DataPilotGroups(**args):
return Element(qname = (TABLENS,'data-pilot-groups'), **args)
def DataPilotLayoutInfo(**args):
return Element(qname = (TABLENS,'data-pilot-layout-info'), **args)
def DataPilotLevel(**args):
return Element(qname = (TABLENS,'data-pilot-level'), **args)
def DataPilotMember(**args):
return Element(qname = (TABLENS,'data-pilot-member'), **args)
def DataPilotMembers(**args):
return Element(qname = (TABLENS,'data-pilot-members'), **args)
def DataPilotSortInfo(**args):
return Element(qname = (TABLENS,'data-pilot-sort-info'), **args)
def DataPilotSubtotal(**args):
return Element(qname = (TABLENS,'data-pilot-subtotal'), **args)
def DataPilotSubtotals(**args):
return Element(qname = (TABLENS,'data-pilot-subtotals'), **args)
def DataPilotTable(**args):
return Element(qname = (TABLENS,'data-pilot-table'), **args)
def DataPilotTables(**args):
return Element(qname = (TABLENS,'data-pilot-tables'), **args)
def DatabaseRange(**args):
return Element(qname = (TABLENS,'database-range'), **args)
def DatabaseRanges(**args):
return Element(qname = (TABLENS,'database-ranges'), **args)
def DatabaseSourceQuery(**args):
return Element(qname = (TABLENS,'database-source-query'), **args)
def DatabaseSourceSql(**args):
return Element(qname = (TABLENS,'database-source-sql'), **args)
def DatabaseSourceTable(**args):
return Element(qname = (TABLENS,'database-source-table'), **args)
def DdeLink(**args):
return Element(qname = (TABLENS,'dde-link'), **args)
def DdeLinks(**args):
return Element(qname = (TABLENS,'dde-links'), **args)
def Deletion(**args):
return Element(qname = (TABLENS,'deletion'), **args)
def Deletions(**args):
return Element(qname = (TABLENS,'deletions'), **args)
def Dependencies(**args):
return Element(qname = (TABLENS,'dependencies'), **args)
def Dependency(**args):
return Element(qname = (TABLENS,'dependency'), **args)
def Detective(**args):
return Element(qname = (TABLENS,'detective'), **args)
def ErrorMacro(**args):
return Element(qname = (TABLENS,'error-macro'), **args)
def ErrorMessage(**args):
return Element(qname = (TABLENS,'error-message'), **args)
def EvenColumns(**args):
return Element(qname = (TABLENS,'even-columns'), **args)
def EvenRows(**args):
return Element(qname = (TABLENS,'even-rows'), **args)
def Filter(**args):
return Element(qname = (TABLENS,'filter'), **args)
def FilterAnd(**args):
return Element(qname = (TABLENS,'filter-and'), **args)
def FilterCondition(**args):
return Element(qname = (TABLENS,'filter-condition'), **args)
def FilterOr(**args):
return Element(qname = (TABLENS,'filter-or'), **args)
def FirstColumn(**args):
return Element(qname = (TABLENS,'first-column'), **args)
def FirstRow(**args):
return Element(qname = (TABLENS,'first-row'), **args)
def HelpMessage(**args):
return Element(qname = (TABLENS,'help-message'), **args)
def HighlightedRange(**args):
return Element(qname = (TABLENS,'highlighted-range'), **args)
def Insertion(**args):
return Element(qname = (TABLENS,'insertion'), **args)
def InsertionCutOff(**args):
return Element(qname = (TABLENS,'insertion-cut-off'), **args)
def Iteration(**args):
return Element(qname = (TABLENS,'iteration'), **args)
def LabelRange(**args):
return Element(qname = (TABLENS,'label-range'), **args)
def LabelRanges(**args):
return Element(qname = (TABLENS,'label-ranges'), **args)
def LastColumn(**args):
return Element(qname = (TABLENS,'last-column'), **args)
def LastRow(**args):
return Element(qname = (TABLENS,'last-row'), **args)
def Movement(**args):
return Element(qname = (TABLENS,'movement'), **args)
def MovementCutOff(**args):
return Element(qname = (TABLENS,'movement-cut-off'), **args)
def NamedExpression(**args):
return Element(qname = (TABLENS,'named-expression'), **args)
def NamedExpressions(**args):
return Element(qname = (TABLENS,'named-expressions'), **args)
def NamedRange(**args):
return Element(qname = (TABLENS,'named-range'), **args)
def NullDate(**args):
return Element(qname = (TABLENS,'null-date'), **args)
def OddColumns(**args):
return Element(qname = (TABLENS,'odd-columns'), **args)
def OddRows(**args):
return Element(qname = (TABLENS,'odd-rows'), **args)
def Operation(**args):
return Element(qname = (TABLENS,'operation'), **args)
def Previous(**args):
return Element(qname = (TABLENS,'previous'), **args)
def Scenario(**args):
return Element(qname = (TABLENS,'scenario'), **args)
def Shapes(**args):
return Element(qname = (TABLENS,'shapes'), **args)
def Sort(**args):
return Element(qname = (TABLENS,'sort'), **args)
def SortBy(**args):
return Element(qname = (TABLENS,'sort-by'), **args)
def SortGroups(**args):
return Element(qname = (TABLENS,'sort-groups'), **args)
def SourceCellRange(**args):
return Element(qname = (TABLENS,'source-cell-range'), **args)
def SourceRangeAddress(**args):
return Element(qname = (TABLENS,'source-range-address'), **args)
def SourceService(**args):
return Element(qname = (TABLENS,'source-service'), **args)
def SubtotalField(**args):
return Element(qname = (TABLENS,'subtotal-field'), **args)
def SubtotalRule(**args):
return Element(qname = (TABLENS,'subtotal-rule'), **args)
def SubtotalRules(**args):
return Element(qname = (TABLENS,'subtotal-rules'), **args)
def Table(**args):
return Element(qname = (TABLENS,'table'), **args)
def TableCell(**args):
return Element(qname = (TABLENS,'table-cell'), **args)
def TableColumn(**args):
return Element(qname = (TABLENS,'table-column'), **args)
def TableColumnGroup(**args):
return Element(qname = (TABLENS,'table-column-group'), **args)
def TableColumns(**args):
return Element(qname = (TABLENS,'table-columns'), **args)
def TableHeaderColumns(**args):
return Element(qname = (TABLENS,'table-header-columns'), **args)
def TableHeaderRows(**args):
return Element(qname = (TABLENS,'table-header-rows'), **args)
def TableRow(**args):
return Element(qname = (TABLENS,'table-row'), **args)
def TableRowGroup(**args):
return Element(qname = (TABLENS,'table-row-group'), **args)
def TableRows(**args):
return Element(qname = (TABLENS,'table-rows'), **args)
def TableSource(**args):
return Element(qname = (TABLENS,'table-source'), **args)
def TableTemplate(**args):
return Element(qname = (TABLENS,'table-template'), **args)
def TargetRangeAddress(**args):
return Element(qname = (TABLENS,'target-range-address'), **args)
def TrackedChanges(**args):
return Element(qname = (TABLENS,'tracked-changes'), **args)

137
src/odf/teletype.py Normal file
View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#
# Create and extract text from ODF, handling whitespace correctly.
# Copyright (C) 2008 J. David Eisenberg
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, 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.
"""
Class for handling whitespace properly in OpenDocument.
While it is possible to use getTextContent() and setTextContent()
to extract or create ODF content, these won't extract or create
the appropriate <text:s>, <text:tab>, or <text:line-break>
elements. This module takes care of that problem.
"""
from odf.element import Node
import odf.opendocument
from odf.text import S,LineBreak,Tab
class WhitespaceText(object):
def __init__(self):
self.textBuffer = []
self.spaceCount = 0
def addTextToElement(self, odfElement, s):
""" Process an input string, inserting
<text:tab> elements for '\t',
<text:line-break> elements for '\n', and
<text:s> elements for runs of more than one blank.
These will be added to the given element.
"""
i = 0
ch = ' '
# When we encounter a tab or newline, we can immediately
# dump any accumulated text and then emit the appropriate
# ODF element.
#
# When we encounter a space, we add it to the text buffer,
# and then collect more spaces. If there are more spaces
# after the first one, we dump the text buffer and then
# then emit the appropriate <text:s> element.
while i < len(s):
ch = s[i]
if ch == '\t':
self._emitTextBuffer(odfElement)
odfElement.addElement(Tab())
i += 1
elif ch == '\n':
self._emitTextBuffer(odfElement);
odfElement.addElement(LineBreak())
i += 1
elif ch == ' ':
self.textBuffer.append(' ')
i += 1
self.spaceCount = 0
while i < len(s) and (s[i] == ' '):
self.spaceCount += 1
i += 1
if self.spaceCount > 0:
self._emitTextBuffer(odfElement)
self._emitSpaces(odfElement)
else:
self.textBuffer.append(ch)
i += 1
self._emitTextBuffer(odfElement)
def _emitTextBuffer(self, odfElement):
""" Creates a Text Node whose contents are the current textBuffer.
Side effect: clears the text buffer.
"""
if len(self.textBuffer) > 0:
odfElement.addText(''.join(self.textBuffer))
self.textBuffer = []
def _emitSpaces(self, odfElement):
""" Creates a <text:s> element for the current spaceCount.
Side effect: sets spaceCount back to zero
"""
if self.spaceCount > 0:
spaceElement = S(c=self.spaceCount)
odfElement.addElement(spaceElement)
self.spaceCount = 0
def addTextToElement(odfElement, s):
wst = WhitespaceText()
wst.addTextToElement(odfElement, s)
def extractText(odfElement):
""" Extract text content from an Element, with whitespace represented
properly. Returns the text, with tabs, spaces, and newlines
correctly evaluated. This method recursively descends through the
children of the given element, accumulating text and "unwrapping"
<text:s>, <text:tab>, and <text:line-break> elements along the way.
"""
result = [];
if len(odfElement.childNodes) != 0:
for child in odfElement.childNodes:
if child.nodeType == Node.TEXT_NODE:
result.append(child.data)
elif child.nodeType == Node.ELEMENT_NODE:
subElement = child
tagName = subElement.qname;
if tagName == (u"urn:oasis:names:tc:opendocument:xmlns:text:1.0", u"line-break"):
result.append("\n")
elif tagName == (u"urn:oasis:names:tc:opendocument:xmlns:text:1.0", u"tab"):
result.append("\t")
elif tagName == (u"urn:oasis:names:tc:opendocument:xmlns:text:1.0", u"s"):
c = subElement.getAttribute('c')
if c:
spaceCount = int(c)
else:
spaceCount = 1
result.append(" " * spaceCount)
else:
result.append(extractText(subElement))
return ''.join(result)

559
src/odf/text.py Normal file
View File

@ -0,0 +1,559 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import TEXTNS
from element import Element
from style import StyleElement
# Autogenerated
def A(**args):
return Element(qname = (TEXTNS,'a'), **args)
def AlphabeticalIndex(**args):
return Element(qname = (TEXTNS,'alphabetical-index'), **args)
def AlphabeticalIndexAutoMarkFile(**args):
return Element(qname = (TEXTNS,'alphabetical-index-auto-mark-file'), **args)
def AlphabeticalIndexEntryTemplate(**args):
return Element(qname = (TEXTNS,'alphabetical-index-entry-template'), **args)
def AlphabeticalIndexMark(**args):
return Element(qname = (TEXTNS,'alphabetical-index-mark'), **args)
def AlphabeticalIndexMarkEnd(**args):
return Element(qname = (TEXTNS,'alphabetical-index-mark-end'), **args)
def AlphabeticalIndexMarkStart(**args):
return Element(qname = (TEXTNS,'alphabetical-index-mark-start'), **args)
def AlphabeticalIndexSource(**args):
return Element(qname = (TEXTNS,'alphabetical-index-source'), **args)
def AuthorInitials(**args):
return Element(qname = (TEXTNS,'author-initials'), **args)
def AuthorName(**args):
return Element(qname = (TEXTNS,'author-name'), **args)
def Bibliography(**args):
return Element(qname = (TEXTNS,'bibliography'), **args)
def BibliographyConfiguration(**args):
return Element(qname = (TEXTNS,'bibliography-configuration'), **args)
def BibliographyEntryTemplate(**args):
return Element(qname = (TEXTNS,'bibliography-entry-template'), **args)
def BibliographyMark(**args):
return Element(qname = (TEXTNS,'bibliography-mark'), **args)
def BibliographySource(**args):
return Element(qname = (TEXTNS,'bibliography-source'), **args)
def Bookmark(**args):
return Element(qname = (TEXTNS,'bookmark'), **args)
def BookmarkEnd(**args):
return Element(qname = (TEXTNS,'bookmark-end'), **args)
def BookmarkRef(**args):
return Element(qname = (TEXTNS,'bookmark-ref'), **args)
def BookmarkStart(**args):
return Element(qname = (TEXTNS,'bookmark-start'), **args)
def Change(**args):
return Element(qname = (TEXTNS,'change'), **args)
def ChangeEnd(**args):
return Element(qname = (TEXTNS,'change-end'), **args)
def ChangeStart(**args):
return Element(qname = (TEXTNS,'change-start'), **args)
def ChangedRegion(**args):
return Element(qname = (TEXTNS,'changed-region'), **args)
def Chapter(**args):
return Element(qname = (TEXTNS,'chapter'), **args)
def CharacterCount(**args):
return Element(qname = (TEXTNS,'character-count'), **args)
def ConditionalText(**args):
return Element(qname = (TEXTNS,'conditional-text'), **args)
def CreationDate(**args):
return Element(qname = (TEXTNS,'creation-date'), **args)
def CreationTime(**args):
return Element(qname = (TEXTNS,'creation-time'), **args)
def Creator(**args):
return Element(qname = (TEXTNS,'creator'), **args)
def DatabaseDisplay(**args):
return Element(qname = (TEXTNS,'database-display'), **args)
def DatabaseName(**args):
return Element(qname = (TEXTNS,'database-name'), **args)
def DatabaseNext(**args):
return Element(qname = (TEXTNS,'database-next'), **args)
def DatabaseRowNumber(**args):
return Element(qname = (TEXTNS,'database-row-number'), **args)
def DatabaseRowSelect(**args):
return Element(qname = (TEXTNS,'database-row-select'), **args)
def Date(**args):
return Element(qname = (TEXTNS,'date'), **args)
def DdeConnection(**args):
return Element(qname = (TEXTNS,'dde-connection'), **args)
def DdeConnectionDecl(**args):
return Element(qname = (TEXTNS,'dde-connection-decl'), **args)
def DdeConnectionDecls(**args):
return Element(qname = (TEXTNS,'dde-connection-decls'), **args)
def Deletion(**args):
return Element(qname = (TEXTNS,'deletion'), **args)
def Description(**args):
return Element(qname = (TEXTNS,'description'), **args)
def EditingCycles(**args):
return Element(qname = (TEXTNS,'editing-cycles'), **args)
def EditingDuration(**args):
return Element(qname = (TEXTNS,'editing-duration'), **args)
def ExecuteMacro(**args):
return Element(qname = (TEXTNS,'execute-macro'), **args)
def Expression(**args):
return Element(qname = (TEXTNS,'expression'), **args)
def FileName(**args):
return Element(qname = (TEXTNS,'file-name'), **args)
def FormatChange(**args):
return Element(qname = (TEXTNS,'format-change'), **args)
def H(**args):
return Element(qname = (TEXTNS, 'h'), **args)
def HiddenParagraph(**args):
return Element(qname = (TEXTNS,'hidden-paragraph'), **args)
def HiddenText(**args):
return Element(qname = (TEXTNS,'hidden-text'), **args)
def IllustrationIndex(**args):
return Element(qname = (TEXTNS,'illustration-index'), **args)
def IllustrationIndexEntryTemplate(**args):
return Element(qname = (TEXTNS,'illustration-index-entry-template'), **args)
def IllustrationIndexSource(**args):
return Element(qname = (TEXTNS,'illustration-index-source'), **args)
def ImageCount(**args):
return Element(qname = (TEXTNS,'image-count'), **args)
def IndexBody(**args):
return Element(qname = (TEXTNS,'index-body'), **args)
def IndexEntryBibliography(**args):
return Element(qname = (TEXTNS,'index-entry-bibliography'), **args)
def IndexEntryChapter(**args):
return Element(qname = (TEXTNS,'index-entry-chapter'), **args)
def IndexEntryLinkEnd(**args):
return Element(qname = (TEXTNS,'index-entry-link-end'), **args)
def IndexEntryLinkStart(**args):
return Element(qname = (TEXTNS,'index-entry-link-start'), **args)
def IndexEntryPageNumber(**args):
return Element(qname = (TEXTNS,'index-entry-page-number'), **args)
def IndexEntrySpan(**args):
return Element(qname = (TEXTNS,'index-entry-span'), **args)
def IndexEntryTabStop(**args):
return Element(qname = (TEXTNS,'index-entry-tab-stop'), **args)
def IndexEntryText(**args):
return Element(qname = (TEXTNS,'index-entry-text'), **args)
def IndexSourceStyle(**args):
return Element(qname = (TEXTNS,'index-source-style'), **args)
def IndexSourceStyles(**args):
return Element(qname = (TEXTNS,'index-source-styles'), **args)
def IndexTitle(**args):
return Element(qname = (TEXTNS,'index-title'), **args)
def IndexTitleTemplate(**args):
return Element(qname = (TEXTNS,'index-title-template'), **args)
def InitialCreator(**args):
return Element(qname = (TEXTNS,'initial-creator'), **args)
def Insertion(**args):
return Element(qname = (TEXTNS,'insertion'), **args)
def Keywords(**args):
return Element(qname = (TEXTNS,'keywords'), **args)
def LineBreak(**args):
return Element(qname = (TEXTNS,'line-break'), **args)
def LinenumberingConfiguration(**args):
return Element(qname = (TEXTNS,'linenumbering-configuration'), **args)
def LinenumberingSeparator(**args):
return Element(qname = (TEXTNS,'linenumbering-separator'), **args)
def List(**args):
return Element(qname = (TEXTNS,'list'), **args)
def ListHeader(**args):
return Element(qname = (TEXTNS,'list-header'), **args)
def ListItem(**args):
return Element(qname = (TEXTNS,'list-item'), **args)
def ListLevelStyleBullet(**args):
return Element(qname = (TEXTNS,'list-level-style-bullet'), **args)
def ListLevelStyleImage(**args):
return Element(qname = (TEXTNS,'list-level-style-image'), **args)
def ListLevelStyleNumber(**args):
return Element(qname = (TEXTNS,'list-level-style-number'), **args)
def ListStyle(**args):
return StyleElement(qname = (TEXTNS,'list-style'), **args)
def Measure(**args):
return Element(qname = (TEXTNS,'measure'), **args)
def ModificationDate(**args):
return Element(qname = (TEXTNS,'modification-date'), **args)
def ModificationTime(**args):
return Element(qname = (TEXTNS,'modification-time'), **args)
def Note(**args):
return Element(qname = (TEXTNS,'note'), **args)
def NoteBody(**args):
return Element(qname = (TEXTNS,'note-body'), **args)
def NoteCitation(**args):
return Element(qname = (TEXTNS,'note-citation'), **args)
def NoteContinuationNoticeBackward(**args):
return Element(qname = (TEXTNS,'note-continuation-notice-backward'), **args)
def NoteContinuationNoticeForward(**args):
return Element(qname = (TEXTNS,'note-continuation-notice-forward'), **args)
def NoteRef(**args):
return Element(qname = (TEXTNS,'note-ref'), **args)
def NotesConfiguration(**args):
return Element(qname = (TEXTNS,'notes-configuration'), **args)
def Number(**args):
return Element(qname = (TEXTNS,'number'), **args)
def NumberedParagraph(**args):
return Element(qname = (TEXTNS,'numbered-paragraph'), **args)
def ObjectCount(**args):
return Element(qname = (TEXTNS,'object-count'), **args)
def ObjectIndex(**args):
return Element(qname = (TEXTNS,'object-index'), **args)
def ObjectIndexEntryTemplate(**args):
return Element(qname = (TEXTNS,'object-index-entry-template'), **args)
def ObjectIndexSource(**args):
return Element(qname = (TEXTNS,'object-index-source'), **args)
def OutlineLevelStyle(**args):
return Element(qname = (TEXTNS,'outline-level-style'), **args)
def OutlineStyle(**args):
return Element(qname = (TEXTNS,'outline-style'), **args)
def P(**args):
return Element(qname = (TEXTNS, 'p'), **args)
def Page(**args):
return Element(qname = (TEXTNS,'page'), **args)
def PageContinuation(**args):
return Element(qname = (TEXTNS,'page-continuation'), **args)
def PageCount(**args):
return Element(qname = (TEXTNS,'page-count'), **args)
def PageNumber(**args):
return Element(qname = (TEXTNS,'page-number'), **args)
def PageSequence(**args):
return Element(qname = (TEXTNS,'page-sequence'), **args)
def PageVariableGet(**args):
return Element(qname = (TEXTNS,'page-variable-get'), **args)
def PageVariableSet(**args):
return Element(qname = (TEXTNS,'page-variable-set'), **args)
def ParagraphCount(**args):
return Element(qname = (TEXTNS,'paragraph-count'), **args)
def Placeholder(**args):
return Element(qname = (TEXTNS,'placeholder'), **args)
def PrintDate(**args):
return Element(qname = (TEXTNS,'print-date'), **args)
def PrintTime(**args):
return Element(qname = (TEXTNS,'print-time'), **args)
def PrintedBy(**args):
return Element(qname = (TEXTNS,'printed-by'), **args)
def ReferenceMark(**args):
return Element(qname = (TEXTNS,'reference-mark'), **args)
def ReferenceMarkEnd(**args):
return Element(qname = (TEXTNS,'reference-mark-end'), **args)
def ReferenceMarkStart(**args):
return Element(qname = (TEXTNS,'reference-mark-start'), **args)
def ReferenceRef(**args):
return Element(qname = (TEXTNS,'reference-ref'), **args)
def Ruby(**args):
return Element(qname = (TEXTNS,'ruby'), **args)
def RubyBase(**args):
return Element(qname = (TEXTNS,'ruby-base'), **args)
def RubyText(**args):
return Element(qname = (TEXTNS,'ruby-text'), **args)
def S(**args):
return Element(qname = (TEXTNS,'s'), **args)
def Script(**args):
return Element(qname = (TEXTNS,'script'), **args)
def Section(**args):
return Element(qname = (TEXTNS,'section'), **args)
def SectionSource(**args):
return Element(qname = (TEXTNS,'section-source'), **args)
def SenderCity(**args):
return Element(qname = (TEXTNS,'sender-city'), **args)
def SenderCompany(**args):
return Element(qname = (TEXTNS,'sender-company'), **args)
def SenderCountry(**args):
return Element(qname = (TEXTNS,'sender-country'), **args)
def SenderEmail(**args):
return Element(qname = (TEXTNS,'sender-email'), **args)
def SenderFax(**args):
return Element(qname = (TEXTNS,'sender-fax'), **args)
def SenderFirstname(**args):
return Element(qname = (TEXTNS,'sender-firstname'), **args)
def SenderInitials(**args):
return Element(qname = (TEXTNS,'sender-initials'), **args)
def SenderLastname(**args):
return Element(qname = (TEXTNS,'sender-lastname'), **args)
def SenderPhonePrivate(**args):
return Element(qname = (TEXTNS,'sender-phone-private'), **args)
def SenderPhoneWork(**args):
return Element(qname = (TEXTNS,'sender-phone-work'), **args)
def SenderPosition(**args):
return Element(qname = (TEXTNS,'sender-position'), **args)
def SenderPostalCode(**args):
return Element(qname = (TEXTNS,'sender-postal-code'), **args)
def SenderStateOrProvince(**args):
return Element(qname = (TEXTNS,'sender-state-or-province'), **args)
def SenderStreet(**args):
return Element(qname = (TEXTNS,'sender-street'), **args)
def SenderTitle(**args):
return Element(qname = (TEXTNS,'sender-title'), **args)
def Sequence(**args):
return Element(qname = (TEXTNS,'sequence'), **args)
def SequenceDecl(**args):
return Element(qname = (TEXTNS,'sequence-decl'), **args)
def SequenceDecls(**args):
return Element(qname = (TEXTNS,'sequence-decls'), **args)
def SequenceRef(**args):
return Element(qname = (TEXTNS,'sequence-ref'), **args)
def SheetName(**args):
return Element(qname = (TEXTNS,'sheet-name'), **args)
def SortKey(**args):
return Element(qname = (TEXTNS,'sort-key'), **args)
def Span(**args):
return Element(qname = (TEXTNS,'span'), **args)
def Subject(**args):
return Element(qname = (TEXTNS,'subject'), **args)
def Tab(**args):
return Element(qname = (TEXTNS,'tab'), **args)
def TableCount(**args):
return Element(qname = (TEXTNS,'table-count'), **args)
def TableFormula(**args):
return Element(qname = (TEXTNS,'table-formula'), **args)
def TableIndex(**args):
return Element(qname = (TEXTNS,'table-index'), **args)
def TableIndexEntryTemplate(**args):
return Element(qname = (TEXTNS,'table-index-entry-template'), **args)
def TableIndexSource(**args):
return Element(qname = (TEXTNS,'table-index-source'), **args)
def TableOfContent(**args):
return Element(qname = (TEXTNS,'table-of-content'), **args)
def TableOfContentEntryTemplate(**args):
return Element(qname = (TEXTNS,'table-of-content-entry-template'), **args)
def TableOfContentSource(**args):
return Element(qname = (TEXTNS,'table-of-content-source'), **args)
def TemplateName(**args):
return Element(qname = (TEXTNS,'template-name'), **args)
def TextInput(**args):
return Element(qname = (TEXTNS,'text-input'), **args)
def Time(**args):
return Element(qname = (TEXTNS,'time'), **args)
def Title(**args):
return Element(qname = (TEXTNS,'title'), **args)
def TocMark(**args):
return Element(qname = (TEXTNS,'toc-mark'), **args)
def TocMarkEnd(**args):
return Element(qname = (TEXTNS,'toc-mark-end'), **args)
def TocMarkStart(**args):
return Element(qname = (TEXTNS,'toc-mark-start'), **args)
def TrackedChanges(**args):
return Element(qname = (TEXTNS,'tracked-changes'), **args)
def UserDefined(**args):
return Element(qname = (TEXTNS,'user-defined'), **args)
def UserFieldDecl(**args):
return Element(qname = (TEXTNS,'user-field-decl'), **args)
def UserFieldDecls(**args):
return Element(qname = (TEXTNS,'user-field-decls'), **args)
def UserFieldGet(**args):
return Element(qname = (TEXTNS,'user-field-get'), **args)
def UserFieldInput(**args):
return Element(qname = (TEXTNS,'user-field-input'), **args)
def UserIndex(**args):
return Element(qname = (TEXTNS,'user-index'), **args)
def UserIndexEntryTemplate(**args):
return Element(qname = (TEXTNS,'user-index-entry-template'), **args)
def UserIndexMark(**args):
return Element(qname = (TEXTNS,'user-index-mark'), **args)
def UserIndexMarkEnd(**args):
return Element(qname = (TEXTNS,'user-index-mark-end'), **args)
def UserIndexMarkStart(**args):
return Element(qname = (TEXTNS,'user-index-mark-start'), **args)
def UserIndexSource(**args):
return Element(qname = (TEXTNS,'user-index-source'), **args)
def VariableDecl(**args):
return Element(qname = (TEXTNS,'variable-decl'), **args)
def VariableDecls(**args):
return Element(qname = (TEXTNS,'variable-decls'), **args)
def VariableGet(**args):
return Element(qname = (TEXTNS,'variable-get'), **args)
def VariableInput(**args):
return Element(qname = (TEXTNS,'variable-input'), **args)
def VariableSet(**args):
return Element(qname = (TEXTNS,'variable-set'), **args)
def WordCount(**args):
return Element(qname = (TEXTNS,'word-count'), **args)

427
src/odf/thumbnail.py Normal file
View File

@ -0,0 +1,427 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This contains a 128x128 px thumbnail in PNG format
# Taken from http://www.zwahlendesign.ch/en/node/20
# openoffice_icons/openoffice_icons_linux/openoffice11.png
# License: Freeware
import base64
iconstr = """\
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
N38FAAAAAElFTkSuQmCC\
"""
def thumbnail():
icon = base64.decodestring(iconstr)
return icon
if __name__ == "__main__":
icon = thumbnail()
f = file("thumbnail.png","wb")
f.write(icon)
f.close()

322
src/odf/userfield.py Normal file
View File

@ -0,0 +1,322 @@
#!/usr/bin/python
# -*- 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
#
# $Id: userfield.py 447 2008-07-10 20:01:30Z roug $
"""Class to show and manipulate user fields in odf documents."""
import sys
import time
import zipfile
import xml.sax
import xml.sax.handler
import xml.sax.saxutils
from odf.namespaces import OFFICENS, TEXTNS
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
OUTENCODING = "utf-8"
# OpenDocument v.1.0 section 6.7.1
VALUE_TYPES = {
'float': (OFFICENS, u'value'),
'percentage': (OFFICENS, u'value'),
'currency': (OFFICENS, u'value'),
'date': (OFFICENS, u'date-value'),
'time': (OFFICENS, u'time-value'),
'boolean': (OFFICENS, u'boolean-value'),
'string': (OFFICENS, u'string-value'),
}
class UserFields(object):
"""List, view and manipulate user fields."""
# these attributes can be a filename or a file like object
src_file = None
dest_file = None
def __init__(self, src=None, dest=None):
"""Constructor
src ... source document name, file like object or None for stdin
dest ... destination document name, file like object or None for stdout
"""
self.src_file = src
self.dest_file = dest
def list_fields(self):
"""List (extract) all known user-fields.
Returns list of user-field names.
"""
return [x[0] for x in self.list_fields_and_values()]
def list_fields_and_values(self, field_names=None):
"""List (extract) user-fields with type and value.
field_names ... list of field names to show or None for all.
Returns list of tuples (<field name>, <field type>, <value>).
"""
found_fields = []
def _callback(field_name, value_type, value, attrs):
if field_names is None or field_name in field_names:
found_fields.append((field_name.encode(OUTENCODING),
value_type.encode(OUTENCODING),
value.encode(OUTENCODING)))
return attrs
self._content_handler(_callback)
return found_fields
def list_values(self, field_names):
"""Extract the contents of given field names from the file.
field_names ... list of field names
Returns list of field values.
"""
return [x[2] for x in self.list_fields_and_values(field_names)]
def get(self, field_name):
"""Extract the contents of this field from the file.
Returns field value or None if field does not exist.
"""
values = self.list_values([field_name])
if not values:
return None
return values[0]
def get_type_and_value(self, field_name):
"""Extract the type and contents of this field from the file.
Returns tuple (<type>, <field-value>) or None if field does not exist.
"""
fields = self.list_fields_and_values([field_name])
if not fields:
return None
field_name, value_type, value = fields[0]
return value_type, value
def update(self, data):
"""Set the value of user fields. The field types will be the same.
data ... dict, with field name as key, field value as value
Returns None
"""
def _callback(field_name, value_type, value, attrs):
if field_name in data:
valattr = VALUE_TYPES.get(value_type)
attrs = dict(attrs.items())
# Take advantage that startElementNS can take a normal
# dict as attrs
attrs[valattr] = data[field_name]
return attrs
self._content_handler(_callback, write_file=True)
def _content_handler(self, callback_func, write_file=False):
"""Handle the content using the callback function and write result if
necessary.
callback_func ... function called for each field found in odf document
signature: field_name ... name of current field
value_type ... type of current field
value ... value of current field
attrs ... tuple of attrs of current field
returns: tuple or dict of attrs
write_file ... boolean telling wether write result to file
"""
class DevNull(object):
"""IO-object which behaves like /dev/null."""
def write(self, str):
pass
# get input
if isinstance(self.src_file, basestring):
# src_file is a filename, check if it is a zip-file
if not zipfile.is_zipfile(self.src_file):
raise TypeError("%s is no odt file." % self.src_file)
elif self.src_file is None:
# use stdin if no file given
self.src_file = sys.stdin
zin = zipfile.ZipFile(self.src_file, 'r')
content_xml = zin.read('content.xml')
# prepare output
if write_file:
output_io = StringIO()
if self.dest_file is None:
# use stdout if no filename given
self.dest_file = sys.stdout
zout = zipfile.ZipFile(self.dest_file, 'w')
else:
output_io = DevNull()
# parse input
odfs = ODFContentParser(callback_func, output_io)
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 1)
parser.setContentHandler(odfs)
parser.parse(StringIO(content_xml))
# write output
if write_file:
# Loop through the input zipfile and copy the content to
# the output until we get to the content.xml. Then
# substitute.
for zinfo in zin.infolist():
if zinfo.filename == "content.xml":
# Write meta
zi = zipfile.ZipInfo("content.xml", time.localtime()[:6])
zi.compress_type = zipfile.ZIP_DEFLATED
zout.writestr(zi, odfs.content())
else:
payload = zin.read(zinfo.filename)
zout.writestr(zinfo, payload)
zout.close()
zin.close()
class ODFContentParser(xml.sax.saxutils.XMLGenerator):
def __init__(self, callback_func, out=None, encoding=OUTENCODING):
"""Constructor.
callback_func ... function called for each field found in odf document
signature: field_name ... name of current field
value_type ... type of current field
value ... value of current field
attrs ... tuple of attrs of current field
returns: tuple or dict of attrs
out ... file like object for output
encoding ... encoding for output
"""
self._callback_func = callback_func
xml.sax.saxutils.XMLGenerator.__init__(self, out, encoding)
def startElementNS(self, name, qname, attrs):
if name == (TEXTNS, u'user-field-decl'):
field_name = attrs.get((TEXTNS, u'name'))
value_type = attrs.get((OFFICENS, u'value-type'))
if value_type == 'string':
value = attrs.get((OFFICENS, u'string-value'))
else:
value = attrs.get((OFFICENS, u'value'))
attrs = self._callback_func(field_name, value_type, value, attrs)
self._startElementNS(name, qname, attrs)
def _startElementNS(self, name, qname, attrs):
# copy of xml.sax.saxutils.XMLGenerator.startElementNS
# necessary because we have to provide our own writeattr
# function which is called by this method
if name[0] is None:
name = name[1]
elif self._current_context[name[0]] is None:
# default namespace
name = name[1]
else:
name = self._current_context[name[0]] + ":" + name[1]
self._out.write('<' + name)
for k,v in self._undeclared_ns_maps:
if k is None:
self._out.write(' xmlns="%s"' % (v or ''))
else:
self._out.write(' xmlns:%s="%s"' % (k,v))
self._undeclared_ns_maps = []
for (name, value) in attrs.items():
if name[0] is None:
name = name[1]
elif self._current_context[name[0]] is None:
# default namespace
#If an attribute has a nsuri but not a prefix, we must
#create a prefix and add a nsdecl
prefix = self.GENERATED_PREFIX % self._generated_prefix_ctr
self._generated_prefix_ctr = self._generated_prefix_ctr + 1
name = prefix + ':' + name[1]
self._out.write(' xmlns:%s=%s' % (prefix, quoteattr(name[0])))
self._current_context[name[0]] = prefix
else:
name = self._current_context[name[0]] + ":" + name[1]
self._out.write(' %s=' % name)
writeattr(self._out, value)
self._out.write('>')
def content(self):
return self._out.getvalue()
ATTR_ENTITIES = {
'\n': '&#x0a;' # convert newlines into entities inside attributes
}
def writetext(stream, text, entities={}):
text = xml.sax.saxutils.escape(text, entities)
try:
stream.write(text)
except UnicodeError:
for c in text:
try:
stream.write(c)
except UnicodeError:
stream.write(u"&#%d;" % ord(c))
def writeattr(stream, text):
# copied from xml.sax.saxutils.writeattr added support for an
# additional entity mapping
countdouble = text.count('"')
entities = ATTR_ENTITIES.copy()
if countdouble:
countsingle = text.count("'")
if countdouble <= countsingle:
entities['"'] = "&quot;"
quote = '"'
else:
entities["'"] = "&apos;"
quote = "'"
else:
quote = '"'
stream.write(quote)
writetext(stream, text, entities)
stream.write(quote)

29
src/odf/xforms.py Normal file
View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2006-2007 Søren Roug, European Environment Agency
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Contributor(s):
#
from namespaces import XFORMSNS
from element import Element
# ODF 1.0 section 11.2
# XForms is designed to be embedded in another XML format.
# Autogenerated
def Model(**args):
return Element(qname = (XFORMSNS,'model'), **args)