element.py
Go to the documentation of this file.
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2007-2010 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 sys, os.path
sys.path.append(os.path.dirname(__file__))
import xml.dom
from xml.dom.minicompat import *
from namespaces import nsdict
import grammar
from attrconverters import AttrConverters
if sys.version_info[0] == 3:
unicode=str # unicode function does not exist
# 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
##
# 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.
#
def _escape(data, entities={}):
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
##
# 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.
#
def _quoteattr(data, entities={}):
entities['\n']='&#10;'
entities['\r']='&#12;'
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
##
# Split a qualified name into namespace part and local part.
def _nssplit(qualifiedName):
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
##
# Complains if you add an element to a parent where it is not allowed
class IllegalChild(Exception):
##
# Complains if you add text or cdata to an element where it is not allowed
class IllegalText(Exception):
##
# super class for more specific nodes
class Node(xml.dom.Node):
parentNode = None
nextSibling = None
previousSibling = None
##
# Tells whether this element has any children; text nodes,
# subelements, whatever.
#
def hasChildNodes(self):
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]
##
# Inserts the node newChild before the existing child node refChild.
# If refChild is null, insert newChild at the end of the list of children.
#
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
##
# Adds the node newChild to the end of the list of children of this node.
# If the newChild is already in the tree, it is first removed.
#
def appendChild(self, newChild):
if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE:
for c in tuple(newChild.childNodes):
self.appendChild(c)
### The DOM does not clearly specify what to return in this case
return newChild
if newChild.nodeType not in self._child_node_types:
raise IllegalChild( "<%s> is not allowed in %s" % ( newChild.tagName, self.tagName))
if newChild.parentNode is not None:
newChild.parentNode.removeChild(newChild)
_append_child(self, newChild)
newChild.nextSibling = None
return newChild
##
# Removes the child node indicated by oldChild from the list of children, and returns it.
#
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
def __str__(self):
val = []
for c in self.childNodes:
val.append(str(c))
return ''.join(val)
def __unicode__(self):
val = []
for c in self.childNodes:
val.append(unicode(c))
return u''.join(val)
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
##
# Mixin that makes childless-ness easy to implement and avoids
# the complexity of the Node methods that deal with children.
#
class Childless:
attributes = None
childNodes = EmptyNodeList()
firstChild = None
lastChild = None
def _get_firstChild(self):
return None
def _get_lastChild(self):
return None
##
# Raises an error
def appendChild(self, node):
raise xml.dom.HierarchyRequestErr(
self.tagName + " nodes cannot have children")
def hasChildNodes(self):
return False
##
# Raises an error
def insertBefore(self, newChild, refChild):
raise xml.dom.HierarchyRequestErr(
self.tagName + " nodes do not have children")
##
# Raises an error
def removeChild(self, oldChild):
raise xml.dom.NotFoundErr(
self.tagName + " nodes do not have children")
##
# Raises an error
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 __unicode__(self):
return self.data
##
# Write XML in UTF-8
def toXml(self,level,f):
if self.data:
f.write(_escape(unicode(self.data)))
class CDATASection(Text, Childless):
nodeType = Node.CDATA_SECTION_NODE
##
# Generate XML output of the node. If the text contains "]]>", then
# escape it by going out of CDATA mode (]]>), then write the string
# and then go into CDATA mode again. (<![CDATA[)
#
def toXml(self,level,f):
if self.data:
f.write('<![CDATA[%s]]>' % self.data.replace(']]>',']]>]]><![CDATA['))
##
# 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.
#
class Element(Node):
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)
prefix = self.get_nsprefix(self.qname[0])
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))
##
# Odfpy maintains a list of known namespaces. In some cases a prefix is used, and
# we need to know which namespace it resolves to.
#
def get_knownns(self, prefix):
global nsdict
for ns,p in nsdict.items():
if p == prefix: return ns
return None
##
# Odfpy maintains a list of known namespaces. In some cases we have a namespace URL,
# and needs to look up or assign the prefix for it.
#
def get_nsprefix(self, namespace):
if namespace is None: namespace = ""
prefix = _nsassign(namespace)
if not namespace in self.namespaces:
self.namespaces[namespace] = prefix
return prefix
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)
##
# adds an element to an Element
#
# Element.addElement(Element)
#
def addElement(self, element, check_grammar=True):
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)
##
# Adds text to an element
# Setting check_grammar=False turns off grammar checking
#
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))
##
# Adds CDATA to an element
# Setting check_grammar=False turns off grammar checking
#
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))
##
# Removes an attribute by name.
def removeAttribute(self, attr, check_grammar=True):
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])
##
# 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.
#
def setAttribute(self, attr, value, check_grammar=True):
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)
##
# 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.
#
def setAttrNS(self, namespace, localpart, value):
allowed_attrs = self.allowed_attributes()
prefix = self.get_nsprefix(namespace)
# 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[(namespace, localpart)] = c.convert((namespace, localpart), value, self)
##
#
# gets an attribute, given a namespace and a key
# @param namespace a unicode string or a bytes: the namespace
# @param localpart a unicode string or a bytes:
# the key to get the attribute
# @return an attribute as a unicode string or a bytes: if both paramters
# are byte strings, it will be a bytes; if both attributes are
# unicode strings, it will be a unicode string
#
def getAttrNS(self, namespace, localpart):
prefix = self.get_nsprefix(namespace)
result = self.attributes.get((namespace, localpart))
assert(
(type(namespace), type(namespace), type(namespace) == \
type(b""), type(b""), type(b"")) or
(type(namespace), type(namespace), type(namespace) == \
type(u""), type(u""), type(u""))
)
return result
def removeAttrNS(self, namespace, localpart):
del self.attributes[(namespace, localpart)]
##
# Get an attribute value. The method knows which namespace the attribute is in
#
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(u' xmlns:' + prefix + u'="'+ _escape(str(namespace))+'"')
for qname in self.attributes.keys():
prefix = self.get_nsprefix(qname[0])
f.write(u' '+_escape(str(prefix+u':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
f.write(u'>')
def write_close_tag(self, level, f):
f.write('</'+self.tagName+'>')
##
#
# Generate an XML stream out of the tree structure
# @param level integer: level in the XML tree; zero at root of the tree
# @param f an open writable file able to accept unicode strings
#
def toXml(self, level, f):
f.write(u'<'+self.tagName)
if level == 0:
for namespace, prefix in self.namespaces.items():
f.write(u' xmlns:' + prefix + u'="'+ _escape(str(namespace))+u'"')
for qname in self.attributes.keys():
prefix = self.get_nsprefix(qname[0])
f.write(u' '+_escape(unicode(prefix+':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
if self.childNodes:
f.write(u'>')
for element in self.childNodes:
element.toXml(level+1,f)
f.write(u'</'+self.tagName+'>')
else:
f.write(u'/>')
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
##
# Gets elements based on the type, which is function from text.py, draw.py etc.
def getElementsByType(self, element):
obj = element(check_grammar=False)
return self._getElementsByObj(obj,[])
##
# This is a check to see if the object is an instance of a type
def isInstanceOf(self, element):
obj = element(check_grammar=False)
return self.qname == obj.qname
odf.element.Element.toXml
def toXml(self, level, f)
Generate an XML stream out of the tree structure.
Definition: element.py:536
odf.element.Childless.replaceChild
def replaceChild(self, newChild, oldChild)
Raises an error.
Definition: element.py:257
odf.element.CDATASection.toXml
def toXml(self, level, f)
Generate XML output of the node.
Definition: element.py:288
odf.element.Element.get_knownns
def get_knownns(self, prefix)
Odfpy maintains a list of known namespaces.
Definition: element.py:357
odf.element.Element.removeAttrNS
def removeAttrNS(self, namespace, localpart)
Definition: element.py:497
odf.element.Element.getAttribute
def getAttribute(self, attr)
Get an attribute value.
Definition: element.py:503
odf.element.Element.allowed_children
allowed_children
Definition: element.py:317
odf.element.Node
super class for more specific nodes
Definition: element.py:101
minicompat
odf.element.Element.addElement
def addElement
adds an element to an Element
Definition: element.py:387
odf.element.Text.__unicode__
def __unicode__(self)
Definition: element.py:271
odf.element.Element.getAttrNS
def getAttrNS(self, namespace, localpart)
gets an attribute, given a namespace and a key
Definition: element.py:484
odf.element.Text
Definition: element.py:261
odf.element.Element.addText
def addText
Adds text to an element Setting check_grammar=False turns off grammar checking.
Definition: element.py:400
odf.element.IllegalText
Complains if you add text or cdata to an element where it is not allowed.
Definition: element.py:97
odf.element.Node.removeChild
def removeChild(self, oldChild)
Removes the child node indicated by oldChild from the list of children, and returns it...
Definition: element.py:176
odf.element.Element.allowed_attributes
def allowed_attributes(self)
Definition: element.py:374
odf.element.Element.attributes
attributes
Definition: element.py:328
odf.element.Element._setOwnerDoc
def _setOwnerDoc(self, element)
Definition: element.py:377
odf.element.Childless.insertBefore
def insertBefore(self, newChild, refChild)
Raises an error.
Definition: element.py:245
odf.element.Element.getElementsByType
def getElementsByType(self, element)
Gets elements based on the type, which is function from text.py, draw.py etc.
Definition: element.py:562
odf.element.Element._getElementsByObj
def _getElementsByObj(self, obj, accumulator)
Definition: element.py:552
odf.element.IllegalChild
Complains if you add an element to a parent where it is not allowed.
Definition: element.py:94
odf.element.Text.toXml
def toXml(self, level, f)
Write XML in UTF-8.
Definition: element.py:276
odf.element.Element.removeAttribute
def removeAttribute
Removes an attribute by name.
Definition: element.py:419
odf.element.Childless
Mixin that makes childless-ness easy to implement and avoids the complexity of the Node methods that ...
Definition: element.py:221
odf.element.Node.__unicode__
def __unicode__(self)
Definition: element.py:198
odf.element.unicode
unicode
Definition: element.py:34
odf.element.Element.__init__
def __init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, args)
Definition: element.py:311
odf.element.Element.setAttrNS
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 t...
Definition: element.py:466
odf.element.Element.setAttribute
def setAttribute
Add an attribute to the element This is sort of a convenience method.
Definition: element.py:443
odf.element.CDATASection
Definition: element.py:280
odf.element.Childless.removeChild
def removeChild(self, oldChild)
Raises an error.
Definition: element.py:251
odf.element.Element.write_open_tag
def write_open_tag(self, level, f)
Definition: element.py:517
odf.element.Childless.hasChildNodes
def hasChildNodes(self)
Definition: element.py:240
Exception
odf.element.Element.ownerDocument
ownerDocument
Definition: element.py:315
odf.element.Text.data
data
Definition: element.py:266
odf.element.Childless.appendChild
def appendChild(self, node)
Raises an error.
Definition: element.py:236
odf.element.Element
Creates a arbitrary element and is intended to be subclassed not used on its own. ...
Definition: element.py:299
odf.element.Element.isInstanceOf
def isInstanceOf(self, element)
This is a check to see if the object is an instance of a type.
Definition: element.py:568
odf.element.Element.write_close_tag
def write_close_tag(self, level, f)
Definition: element.py:527
odf.element.Text.__str__
def __str__(self)
Definition: element.py:268
odf.element.Text.__init__
def __init__(self, data)
Definition: element.py:265
odf.element.Node.appendChild
def appendChild(self, newChild)
Adds the node newChild to the end of the list of children of this node.
Definition: element.py:159
odf.element.Element.addCDATA
def addCDATA
Adds CDATA to an element Setting check_grammar=False turns off grammar checking.
Definition: element.py:411
odf.element.Element.tagName
tagName
Definition: element.py:319
odf.element.Node.__str__
def __str__(self)
Definition: element.py:192
odf.element.Element.get_nsprefix
def get_nsprefix(self, namespace)
Odfpy maintains a list of known namespaces.
Definition: element.py:367
odf.element.Node.insertBefore
def insertBefore(self, newChild, refChild)
Inserts the node newChild before the existing child node refChild.
Definition: element.py:131
odf.element.Element.childNodes
childNodes
Definition: element.py:316
odf.element.Element.qname
qname
Definition: element.py:313
odf.element.Element.namespaces
dictionary namespaces
Definition: element.py:302
odf.element.Node.hasChildNodes
def hasChildNodes(self)
Tells whether this element has any children; text nodes, subelements, whatever.
Definition: element.py:110