ODFPY 1.2.0
 
Loading...
Searching...
No Matches
element.py
Go to the documentation of this file.
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# Copyright (C) 2007-2010 Søren Roug, European Environment Agency
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18#
19# Contributor(s):
20#
21
22# Note: This script has copied a lot of text from xml.dom.minidom.
23# Whatever license applies to that file also applies to this file.
24#
25import sys, os.path
26sys.path.append(os.path.dirname(__file__))
27import re
28import xml.dom
29from xml.dom.minicompat import *
30from odf.namespaces import nsdict
31import odf.grammar as grammar
32from odf.attrconverters import AttrConverters
33
34if sys.version_info[0] == 3:
35 unicode=str # unicode function does not exist
36 unichr=chr # unichr does not exist
37
38
41_xml11_illegal_ranges = (
42 (0x0, 0x0,),
43 (0xd800, 0xdfff,),
44 (0xfffe, 0xffff,),
45)
46
47
50_xml10_illegal_ranges = _xml11_illegal_ranges + (
51 (0x01, 0x08,),
52 (0x0b, 0x0c,),
53 (0x0e, 0x1f,),
54)
55
56
59_xml_discouraged_ranges = (
60 (0x7f, 0x84,),
61 (0x86, 0x9f,),
62)
63
64if sys.maxunicode >= 0x10000:
65 # modern or "wide" python build
66
69 _xml_discouraged_ranges = _xml_discouraged_ranges + (
70 (0x1fffe, 0x1ffff,),
71 (0x2fffe, 0x2ffff,),
72 (0x3fffe, 0x3ffff,),
73 (0x4fffe, 0x4ffff,),
74 (0x5fffe, 0x5ffff,),
75 (0x6fffe, 0x6ffff,),
76 (0x7fffe, 0x7ffff,),
77 (0x8fffe, 0x8ffff,),
78 (0x9fffe, 0x9ffff,),
79 (0xafffe, 0xaffff,),
80 (0xbfffe, 0xbffff,),
81 (0xcfffe, 0xcffff,),
82 (0xdfffe, 0xdffff,),
83 (0xefffe, 0xeffff,),
84 (0xffffe, 0xfffff,),
85 (0x10fffe, 0x10ffff,),
86 )
87# else "narrow" python build - only possible with old versions
88
89def _range_seq_to_re(range_seq):
90 # range pairs are specified as closed intervals
91 return re.compile(u"[{}]".format(
92 u"".join(
93 u"{}-{}".format(re.escape(unichr(lo)), re.escape(unichr(hi)))
94 for lo, hi in range_seq
95 )
96 ), flags=re.UNICODE)
97
98
101_xml_filtered_chars_re = _range_seq_to_re(_xml10_illegal_ranges + _xml_discouraged_ranges)
102
104 return _xml_filtered_chars_re.sub(u"\ufffd", data)
105
106# The following code is pasted form xml.sax.saxutils
107# Tt makes it possible to run the code without the xml sax package installed
108# To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts
109
117def _escape(data, entities={}):
118 data = data.replace("&", "&amp;")
119 data = data.replace("<", "&lt;")
120 data = data.replace(">", "&gt;")
121 for chars, entity in entities.items():
122 data = data.replace(chars, entity)
123 return data
124
125def _sanitize(data, entities={}):
126 return _escape(_handle_unrepresentable(data), entities=entities)
127
128
140def _quoteattr(data, entities={}):
141 entities['\n']='&#10;'
142 entities['\r']='&#12;'
143 data = _sanitize(data, entities)
144 if '"' in data:
145 if "'" in data:
146 data = '"%s"' % data.replace('"', "&quot;")
147 else:
148 data = "'%s'" % data
149 else:
150 data = '"%s"' % data
151 return data
152
153
156def _nssplit(qualifiedName):
157 fields = qualifiedName.split(':', 1)
158 if len(fields) == 2:
159 return fields
160 else:
161 return (None, fields[0])
162
163def _nsassign(namespace):
164 return nsdict.setdefault(namespace,"ns" + str(len(nsdict)))
165
166
167# Exceptions
168
170
171class IllegalText(Exception):
172
173
174class Node(xml.dom.Node):
175 parentNode = None
176 nextSibling = None
177 previousSibling = None
178
179
183 def hasChildNodes(self):
184 if self.childNodes:
185 return True
186 else:
187 return False
188
190 return self.childNodes
191
193 if self.childNodes:
194 return self.childNodes[0]
195
196 def _get_lastChild(self):
197 if self.childNodes:
198 return self.childNodes[-1]
199
200
203 def insertBefore(self, newChild, refChild):
204 if newChild.nodeType not in self._child_node_types:
205 raise IllegalChild( "%s cannot be child of %s" % (newChild.tagName, self.tagName))
206 if newChild.parentNode is not None:
207 newChild.parentNode.removeChild(newChild)
208 if refChild is None:
209 self.appendChild(newChild)
210 else:
211 try:
212 index = self.childNodes.index(refChild)
213 except ValueError:
214 raise xml.dom.NotFoundErr()
215 self.childNodes.insert(index, newChild)
216 newChild.nextSibling = refChild
217 refChild.previousSibling = newChild
218 if index:
219 node = self.childNodes[index-1]
220 node.nextSibling = newChild
221 newChild.previousSibling = node
222 else:
223 newChild.previousSibling = None
224 newChild.parentNode = self
225 return newChild
226
227
230 def appendChild(self, newChild):
231 if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE:
232 for c in tuple(newChild.childNodes):
233 self.appendChild(c)
234
235 return newChild
236 if newChild.nodeType not in self._child_node_types:
237 raise IllegalChild( "<%s> is not allowed in %s" % ( newChild.tagName, self.tagName))
238 if newChild.parentNode is not None:
239 newChild.parentNode.removeChild(newChild)
240 _append_child(self, newChild)
241 newChild.nextSibling = None
242 return newChild
243
244
246 def removeChild(self, oldChild):
247 #FIXME: update ownerDocument.element_dict or find other solution
248 try:
249 self.childNodes.remove(oldChild)
250 except ValueError:
251 raise xml.dom.NotFoundErr()
252 if oldChild.nextSibling is not None:
253 oldChild.nextSibling.previousSibling = oldChild.previousSibling
254 if oldChild.previousSibling is not None:
255 oldChild.previousSibling.nextSibling = oldChild.nextSibling
256 oldChild.nextSibling = oldChild.previousSibling = None
257 if self.ownerDocument:
258 self.ownerDocument.remove_from_caches(oldChild)
259 oldChild.parentNode = None
260 return oldChild
261
262 def __str__(self):
263 val = []
264 for c in self.childNodes:
265 val.append(str(c))
266 return ''.join(val)
267
268 def __unicode__(self):
269 val = []
270 for c in self.childNodes:
271 val.append(unicode(c))
272 return u''.join(val)
273
274defproperty(Node, "firstChild", doc="First child node, or None.")
275defproperty(Node, "lastChild", doc="Last child node, or None.")
276
277def _append_child(self, node):
278 # fast path with less checks; usable by DOM builders if careful
279 childNodes = self.childNodes
280 if childNodes:
281 last = childNodes[-1]
282 node.__dict__["previousSibling"] = last
283 last.__dict__["nextSibling"] = node
284 childNodes.append(node)
285 node.__dict__["parentNode"] = self
286
287
291
292 attributes = None
293 childNodes = EmptyNodeList()
294 firstChild = None
295 lastChild = None
296
298 return None
299
300 def _get_lastChild(self):
301 return None
302
303
304 def appendChild(self, node):
305 raise xml.dom.HierarchyRequestErr(
306 self.tagName + " nodes cannot have children")
307
308 def hasChildNodes(self):
309 return False
310
311
312 def insertBefore(self, newChild, refChild):
313 raise xml.dom.HierarchyRequestErr(
314 self.tagName + " nodes do not have children")
315
316
317 def removeChild(self, oldChild):
318 raise xml.dom.NotFoundErr(
319 self.tagName + " nodes do not have children")
320
321
322 def replaceChild(self, newChild, oldChild):
323 raise xml.dom.HierarchyRequestErr(
324 self.tagName + " nodes do not have children")
325
327 nodeType = Node.TEXT_NODE
328 tagName = "Text"
329
330 def __init__(self, data):
331 self.data = data
332
333 def __str__(self):
334 return self.data
335
336 def __unicode__(self):
337 return self.data
338
339
340 def toXml(self,level,f):
341 if self.data:
342 f.write(_sanitize(unicode(self.data)))
343
345 nodeType = Node.CDATA_SECTION_NODE
346
347
351 def toXml(self,level,f):
352 if self.data:
353 f.write('<![CDATA[%s]]>' % self.data.replace(']]>',']]>]]><![CDATA['))
354
355
362
363 nodeType = Node.ELEMENT_NODE
364 namespaces = {} # Due to shallow copy this is a static variable
365
366
369 _child_node_types = (Node.ELEMENT_NODE,
370 Node.PROCESSING_INSTRUCTION_NODE,
371 Node.COMMENT_NODE,
372 Node.TEXT_NODE,
373 Node.CDATA_SECTION_NODE,
374 Node.ENTITY_REFERENCE_NODE)
375
376 def __init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, **args):
377 if qname is not None:
378 self.qname = qname
379 assert(hasattr(self, 'qname'))
380 self.ownerDocument = None
382 self.allowed_children = grammar.allowed_children.get(self.qname)
383 prefix = self.get_nsprefix(self.qname[0])
384 self.tagNametagName = prefix + ":" + self.qname[1]
385 if text is not None:
386 self.addText(text)
387 if cdata is not None:
388 self.addCDATA(cdata)
389
390 allowed_attrs = self.allowed_attributes()
391 if allowed_attrs is not None:
392 allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
394 # Load the attributes from the 'attributes' argument
395 if attributes:
396 for attr, value in attributes.items():
397 self.setAttribute(attr, value)
398 # Load the qualified attributes
399 if qattributes:
400 for attr, value in qattributes.items():
401 self.setAttrNS(attr[0], attr[1], value)
402 if allowed_attrs is not None:
403 # Load the attributes from the 'args' argument
404 for arg in args.keys():
405 self.setAttribute(arg, args[arg])
406 else:
407 for arg in args.keys(): # If any attribute is allowed
408 self.attributes[arg]=args[arg]
409 if not check_grammar:
410 return
411 # Test that all mandatory attributes have been added.
412 required = grammar.required_attributes.get(self.qname)
413 if required:
414 for r in required:
415 if self.getAttrNS(r[0],r[1]) is None:
416 raise AttributeError( "Required attribute missing: %s in <%s>" % (r[1].lower().replace('-',''), self.tagNametagName))
417
418
421 def get_knownns(self, prefix):
422 global nsdict
423 for ns,p in nsdict.items():
424 if p == prefix: return ns
425 return None
426
427
430 def get_nsprefix(self, namespace):
431 if namespace is None: namespace = ""
432 prefix = _nsassign(namespace)
433 if not namespace in self.namespaces:
434 self.namespaces[namespace] = prefix
435 return prefix
436
438 return grammar.allowed_attributes.get(self.qname)
439
440 def _setOwnerDoc(self, element):
441 element.ownerDocument = self.ownerDocument
442 for child in element.childNodes:
443 self._setOwnerDoc(child)
444
445
451 def addElement(self, element, check_grammar=True):
452 if check_grammar and self.allowed_children is not None:
453 if element.qname not in self.allowed_children:
454 raise IllegalChild( "<%s> is not allowed in <%s>" % ( element.tagName, self.tagNametagName))
455 self.appendChild(element)
456 self._setOwnerDoc(element)
457 if self.ownerDocument:
458 self.ownerDocument.rebuild_caches(element)
459
460
463 def addText(self, text, check_grammar=True):
464 if check_grammar and self.qname not in grammar.allows_text:
465 raise IllegalText( "The <%s> element does not allow text" % self.tagNametagName)
466 else:
467 if text != '':
468 self.appendChild(Text(text))
469
470
473 def addCDATA(self, cdata, check_grammar=True):
474 if check_grammar and self.qname not in grammar.allows_text:
475 raise IllegalText( "The <%s> element does not allow text" % self.tagNametagName)
476 else:
477 self.appendChild(CDATASection(cdata))
478
479
480 def removeAttribute(self, attr, check_grammar=True):
481 allowed_attrs = self.allowed_attributes()
482 if allowed_attrs is None:
483 if type(attr) == type(()):
484 prefix, localname = attr
485 self.removeAttrNS(prefix, localname)
486 else:
487 raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
488 else:
489 # Construct a list of allowed arguments
490 allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
491 if check_grammar and attr not in allowed_args:
492 raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagNametagName))
493 i = allowed_args.index(attr)
494 self.removeAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
495
496
503 def setAttribute(self, attr, value, check_grammar=True):
504 if attr == 'parent' and value is not None:
505 value.addElement(self)
506 else:
507 allowed_attrs = self.allowed_attributes()
508 if allowed_attrs is None:
509 if type(attr) == type(()):
510 prefix, localname = attr
511 self.setAttrNS(prefix, localname, value)
512 else:
513 raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
514 else:
515 # Construct a list of allowed arguments
516 allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
517 if check_grammar and attr not in allowed_args:
518 raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagNametagName))
519 i = allowed_args.index(attr)
520 self.setAttrNS(allowed_attrs[i][0], allowed_attrs[i][1], value)
521
522
528 def setAttrNS(self, namespace, localpart, value):
529 allowed_attrs = self.allowed_attributes()
530 prefix = self.get_nsprefix(namespace)
531# if allowed_attrs and (namespace, localpart) not in allowed_attrs:
532# raise AttributeError( "Attribute %s:%s is not allowed in element <%s>" % ( prefix, localpart, self.tagName))
533 c = AttrConverters()
534 self.attributes[(namespace, localpart)] = c.convert((namespace, localpart), value, self)
535
536
544
545 def getAttrNS(self, namespace, localpart):
546 prefix = self.get_nsprefix(namespace)
547 result = self.attributes.get((namespace, localpart))
548
549 assert(
550 (type(namespace), type(namespace), type(namespace) == \
551 type(b""), type(b""), type(b"")) or
552 (type(namespace), type(namespace), type(namespace) == \
553 type(u""), type(u""), type(u""))
554 )
555
556 return result
557
558 def removeAttrNS(self, namespace, localpart):
559 del self.attributes[(namespace, localpart)]
560
561
563 def getAttribute(self, attr):
564 allowed_attrs = self.allowed_attributes()
565 if allowed_attrs is None:
566 if type(attr) == type(()):
567 prefix, localname = attr
568 return self.getAttrNS(prefix, localname)
569 else:
570 raise AttributeError( "Unable to get simple attribute - use (namespace, localpart)")
571 else:
572 # Construct a list of allowed arguments
573 allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
574 i = allowed_args.index(attr)
575 return self.getAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
576
577 def write_open_tag(self, level, f):
578 f.write(('<'+self.tagNametagName))
579 if level == 0:
580 for namespace, prefix in self.namespaces.items():
581 f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+'"')
582 for qname in self.attributes.keys():
583 prefix = self.get_nsprefix(qname[0])
584 f.write(u' '+_sanitize(str(prefix+u':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
585 f.write(u'>')
586
587 def write_close_tag(self, level, f):
588 f.write('</'+self.tagNametagName+'>')
589
590
594
595 def toXml(self, level, f):
596 f.write(u'<'+self.tagNametagName)
597 if level == 0:
598 for namespace, prefix in self.namespaces.items():
599 f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+u'"')
600 for qname in self.attributes.keys():
601 prefix = self.get_nsprefix(qname[0])
602 f.write(u' '+_sanitize(unicode(prefix+':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
603 if self.childNodes:
604 f.write(u'>')
605 for element in self.childNodes:
606 element.toXml(level+1,f)
607 f.write(u'</'+self.tagNametagName+'>')
608 else:
609 f.write(u'/>')
610
611 def _getElementsByObj(self, obj, accumulator):
612 if self.qname == obj.qname:
613 accumulator.append(self)
614 for e in self.childNodes:
615 if e.nodeType == Node.ELEMENT_NODE:
616 accumulator = e._getElementsByObj(obj, accumulator)
617 return accumulator
618
619
620 def getElementsByType(self, element):
621 obj = element(check_grammar=False)
622 return self._getElementsByObj(obj,[])
623
624
625 def isInstanceOf(self, element):
626 obj = element(check_grammar=False)
627 return self.qname == obj.qname
toXml(self, level, f)
Generate XML output of the node.
Definition element.py:351
Mixin that makes childless-ness easy to implement and avoids the complexity of the Node methods that ...
Definition element.py:290
removeChild(self, oldChild)
Raises an error.
Definition element.py:317
replaceChild(self, newChild, oldChild)
Raises an error.
Definition element.py:322
appendChild(self, node)
Raises an error.
Definition element.py:304
insertBefore(self, newChild, refChild)
Raises an error.
Definition element.py:312
Creates a arbitrary element and is intended to be subclassed not used on its own.
Definition element.py:361
__init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, **args)
Definition element.py:376
get_nsprefix(self, namespace)
Odfpy maintains a list of known namespaces.
Definition element.py:430
getAttrNS(self, namespace, localpart)
gets an attribute, given a namespace and a key
Definition element.py:545
setAttribute(self, attr, value, check_grammar=True)
Add an attribute to the element This is sort of a convenience method.
Definition element.py:503
getElementsByType(self, element)
Gets elements based on the type, which is function from text.py, draw.py etc.
Definition element.py:620
removeAttrNS(self, namespace, localpart)
Definition element.py:558
_getElementsByObj(self, obj, accumulator)
Definition element.py:611
toXml(self, level, f)
Generate an XML stream out of the tree structure.
Definition element.py:595
addElement(self, element, check_grammar=True)
adds an element to an Element
Definition element.py:451
addCDATA(self, cdata, check_grammar=True)
Adds CDATA to an element Setting check_grammar=False turns off grammar checking.
Definition element.py:473
allowed_attributes(self)
Definition element.py:437
write_close_tag(self, level, f)
Definition element.py:587
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:528
_setOwnerDoc(self, element)
Definition element.py:440
isInstanceOf(self, element)
This is a check to see if the object is an instance of a type.
Definition element.py:625
addText(self, text, check_grammar=True)
Adds text to an element Setting check_grammar=False turns off grammar checking.
Definition element.py:463
write_open_tag(self, level, f)
Definition element.py:577
removeAttribute(self, attr, check_grammar=True)
Removes an attribute by name.
Definition element.py:480
get_knownns(self, prefix)
Odfpy maintains a list of known namespaces.
Definition element.py:421
getAttribute(self, attr)
Get an attribute value.
Definition element.py:563
Complains if you add an element to a parent where it is not allowed.
Definition element.py:169
super class for more specific nodes
Definition element.py:171
insertBefore(self, newChild, refChild)
Inserts the node newChild before the existing child node refChild.
Definition element.py:203
removeChild(self, oldChild)
Removes the child node indicated by oldChild from the list of children, and returns it.
Definition element.py:246
_get_firstChild(self)
Definition element.py:192
hasChildNodes(self)
Tells whether this element has any children; text nodes, subelements whatever.
Definition element.py:183
_get_childNodes(self)
Definition element.py:189
_get_lastChild(self)
Definition element.py:196
appendChild(self, newChild)
Adds the node newChild to the end of the list of children of this node.
Definition element.py:230
__unicode__(self)
Definition element.py:268
tagName
The DOM does not clearly specify what to return in this case.
Definition element.py:205
__unicode__(self)
Definition element.py:336
__init__(self, data)
Definition element.py:330
toXml(self, level, f)
Write XML in UTF-8.
Definition element.py:340
_sanitize(data, entities={})
Definition element.py:125
_quoteattr(data, entities={})
Escape and quote an attribute value.
Definition element.py:140
_nsassign(namespace)
Definition element.py:163
_handle_unrepresentable(data)
Definition element.py:103
_nssplit(qualifiedName)
Split a qualified name into namespace part and local part.
Definition element.py:156
_escape(data, entities={})
Escape &, <, and > in a string of data.
Definition element.py:117
_range_seq_to_re(range_seq)
Definition element.py:89
_append_child(self, node)
Definition element.py:277