element.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  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. # Note: This script has copied a lot of text from xml.dom.minidom.
  22. # Whatever license applies to that file also applies to this file.
  23. #
  24. import sys, os.path
  25. sys.path.append(os.path.dirname(__file__))
  26. import re
  27. import xml.dom
  28. from xml.dom.minicompat import *
  29. from odf.namespaces import nsdict
  30. import odf.grammar as grammar
  31. from odf.attrconverters import AttrConverters
  32. if sys.version_info[0] == 3:
  33. unicode=str # unicode function does not exist
  34. unichr=chr # unichr does not exist
  35. _xml11_illegal_ranges = (
  36. (0x0, 0x0,),
  37. (0xd800, 0xdfff,),
  38. (0xfffe, 0xffff,),
  39. )
  40. _xml10_illegal_ranges = _xml11_illegal_ranges + (
  41. (0x01, 0x08,),
  42. (0x0b, 0x0c,),
  43. (0x0e, 0x1f,),
  44. )
  45. _xml_discouraged_ranges = (
  46. (0x7f, 0x84,),
  47. (0x86, 0x9f,),
  48. )
  49. if sys.maxunicode >= 0x10000:
  50. # modern or "wide" python build
  51. _xml_discouraged_ranges = _xml_discouraged_ranges + (
  52. (0x1fffe, 0x1ffff,),
  53. (0x2fffe, 0x2ffff,),
  54. (0x3fffe, 0x3ffff,),
  55. (0x4fffe, 0x4ffff,),
  56. (0x5fffe, 0x5ffff,),
  57. (0x6fffe, 0x6ffff,),
  58. (0x7fffe, 0x7ffff,),
  59. (0x8fffe, 0x8ffff,),
  60. (0x9fffe, 0x9ffff,),
  61. (0xafffe, 0xaffff,),
  62. (0xbfffe, 0xbffff,),
  63. (0xcfffe, 0xcffff,),
  64. (0xdfffe, 0xdffff,),
  65. (0xefffe, 0xeffff,),
  66. (0xffffe, 0xfffff,),
  67. (0x10fffe, 0x10ffff,),
  68. )
  69. # else "narrow" python build - only possible with old versions
  70. def _range_seq_to_re(range_seq):
  71. # range pairs are specified as closed intervals
  72. return re.compile(u"[{}]".format(
  73. u"".join(
  74. u"{}-{}".format(re.escape(unichr(lo)), re.escape(unichr(hi)))
  75. for lo, hi in range_seq
  76. )
  77. ), flags=re.UNICODE)
  78. _xml_filtered_chars_re = _range_seq_to_re(_xml10_illegal_ranges + _xml_discouraged_ranges)
  79. def _handle_unrepresentable(data):
  80. return _xml_filtered_chars_re.sub(u"\ufffd", data)
  81. # The following code is pasted form xml.sax.saxutils
  82. # Tt makes it possible to run the code without the xml sax package installed
  83. # To make it possible to have <rubbish> in your text elements, it is necessary to escape the texts
  84. def _escape(data, entities={}):
  85. """ Escape &, <, and > in a string of data.
  86. You can escape other strings of data by passing a dictionary as
  87. the optional entities parameter. The keys and values must all be
  88. strings; each key will be replaced with its corresponding value.
  89. """
  90. data = data.replace("&", "&amp;")
  91. data = data.replace("<", "&lt;")
  92. data = data.replace(">", "&gt;")
  93. for chars, entity in entities.items():
  94. data = data.replace(chars, entity)
  95. return data
  96. def _sanitize(data, entities={}):
  97. return _escape(_handle_unrepresentable(data), entities=entities)
  98. def _quoteattr(data, entities={}):
  99. """ Escape and quote an attribute value.
  100. Escape &, <, and > in a string of data, then quote it for use as
  101. an attribute value. The \" character will be escaped as well, if
  102. necessary.
  103. You can escape other strings of data by passing a dictionary as
  104. the optional entities parameter. The keys and values must all be
  105. strings; each key will be replaced with its corresponding value.
  106. """
  107. entities['\n']='&#10;'
  108. entities['\r']='&#12;'
  109. data = _sanitize(data, entities)
  110. if '"' in data:
  111. if "'" in data:
  112. data = '"%s"' % data.replace('"', "&quot;")
  113. else:
  114. data = "'%s'" % data
  115. else:
  116. data = '"%s"' % data
  117. return data
  118. def _nssplit(qualifiedName):
  119. """ Split a qualified name into namespace part and local part. """
  120. fields = qualifiedName.split(':', 1)
  121. if len(fields) == 2:
  122. return fields
  123. else:
  124. return (None, fields[0])
  125. def _nsassign(namespace):
  126. return nsdict.setdefault(namespace,"ns" + str(len(nsdict)))
  127. # Exceptions
  128. class IllegalChild(Exception):
  129. """ Complains if you add an element to a parent where it is not allowed """
  130. class IllegalText(Exception):
  131. """ Complains if you add text or cdata to an element where it is not allowed """
  132. class Node(xml.dom.Node):
  133. """ super class for more specific nodes """
  134. parentNode = None
  135. nextSibling = None
  136. previousSibling = None
  137. def hasChildNodes(self):
  138. """ Tells whether this element has any children; text nodes,
  139. subelements, whatever.
  140. """
  141. if self.childNodes:
  142. return True
  143. else:
  144. return False
  145. def _get_childNodes(self):
  146. return self.childNodes
  147. def _get_firstChild(self):
  148. if self.childNodes:
  149. return self.childNodes[0]
  150. def _get_lastChild(self):
  151. if self.childNodes:
  152. return self.childNodes[-1]
  153. def insertBefore(self, newChild, refChild):
  154. """ Inserts the node newChild before the existing child node refChild.
  155. If refChild is null, insert newChild at the end of the list of children.
  156. """
  157. if newChild.nodeType not in self._child_node_types:
  158. raise IllegalChild( "%s cannot be child of %s" % (newChild.tagName, self.tagName))
  159. if newChild.parentNode is not None:
  160. newChild.parentNode.removeChild(newChild)
  161. if refChild is None:
  162. self.appendChild(newChild)
  163. else:
  164. try:
  165. index = self.childNodes.index(refChild)
  166. except ValueError:
  167. raise xml.dom.NotFoundErr()
  168. self.childNodes.insert(index, newChild)
  169. newChild.nextSibling = refChild
  170. refChild.previousSibling = newChild
  171. if index:
  172. node = self.childNodes[index-1]
  173. node.nextSibling = newChild
  174. newChild.previousSibling = node
  175. else:
  176. newChild.previousSibling = None
  177. newChild.parentNode = self
  178. return newChild
  179. def appendChild(self, newChild):
  180. """ Adds the node newChild to the end of the list of children of this node.
  181. If the newChild is already in the tree, it is first removed.
  182. """
  183. if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE:
  184. for c in tuple(newChild.childNodes):
  185. self.appendChild(c)
  186. ### The DOM does not clearly specify what to return in this case
  187. return newChild
  188. if newChild.nodeType not in self._child_node_types:
  189. raise IllegalChild( "<%s> is not allowed in %s" % ( newChild.tagName, self.tagName))
  190. if newChild.parentNode is not None:
  191. newChild.parentNode.removeChild(newChild)
  192. _append_child(self, newChild)
  193. newChild.nextSibling = None
  194. return newChild
  195. def removeChild(self, oldChild):
  196. """ Removes the child node indicated by oldChild from the list of children, and returns it.
  197. """
  198. #FIXME: update ownerDocument.element_dict or find other solution
  199. try:
  200. self.childNodes.remove(oldChild)
  201. except ValueError:
  202. raise xml.dom.NotFoundErr()
  203. if oldChild.nextSibling is not None:
  204. oldChild.nextSibling.previousSibling = oldChild.previousSibling
  205. if oldChild.previousSibling is not None:
  206. oldChild.previousSibling.nextSibling = oldChild.nextSibling
  207. oldChild.nextSibling = oldChild.previousSibling = None
  208. if self.ownerDocument:
  209. self.ownerDocument.remove_from_caches(oldChild)
  210. oldChild.parentNode = None
  211. return oldChild
  212. def __str__(self):
  213. val = []
  214. for c in self.childNodes:
  215. val.append(str(c))
  216. return ''.join(val)
  217. def __unicode__(self):
  218. val = []
  219. for c in self.childNodes:
  220. val.append(unicode(c))
  221. return u''.join(val)
  222. defproperty(Node, "firstChild", doc="First child node, or None.")
  223. defproperty(Node, "lastChild", doc="Last child node, or None.")
  224. def _append_child(self, node):
  225. # fast path with less checks; usable by DOM builders if careful
  226. childNodes = self.childNodes
  227. if childNodes:
  228. last = childNodes[-1]
  229. node.__dict__["previousSibling"] = last
  230. last.__dict__["nextSibling"] = node
  231. childNodes.append(node)
  232. node.__dict__["parentNode"] = self
  233. class Childless:
  234. """ Mixin that makes childless-ness easy to implement and avoids
  235. the complexity of the Node methods that deal with children.
  236. """
  237. attributes = None
  238. childNodes = EmptyNodeList()
  239. firstChild = None
  240. lastChild = None
  241. def _get_firstChild(self):
  242. return None
  243. def _get_lastChild(self):
  244. return None
  245. def appendChild(self, node):
  246. """ Raises an error """
  247. raise xml.dom.HierarchyRequestErr(
  248. self.tagName + " nodes cannot have children")
  249. def hasChildNodes(self):
  250. return False
  251. def insertBefore(self, newChild, refChild):
  252. """ Raises an error """
  253. raise xml.dom.HierarchyRequestErr(
  254. self.tagName + " nodes do not have children")
  255. def removeChild(self, oldChild):
  256. """ Raises an error """
  257. raise xml.dom.NotFoundErr(
  258. self.tagName + " nodes do not have children")
  259. def replaceChild(self, newChild, oldChild):
  260. """ Raises an error """
  261. raise xml.dom.HierarchyRequestErr(
  262. self.tagName + " nodes do not have children")
  263. class Text(Childless, Node):
  264. nodeType = Node.TEXT_NODE
  265. tagName = "Text"
  266. def __init__(self, data):
  267. self.data = data
  268. def __str__(self):
  269. return self.data
  270. def __unicode__(self):
  271. return self.data
  272. def toXml(self,level,f):
  273. """ Write XML in UTF-8 """
  274. if self.data:
  275. f.write(_sanitize(unicode(self.data)))
  276. class CDATASection(Text, Childless):
  277. nodeType = Node.CDATA_SECTION_NODE
  278. def toXml(self,level,f):
  279. """ Generate XML output of the node. If the text contains "]]>", then
  280. escape it by going out of CDATA mode (]]>), then write the string
  281. and then go into CDATA mode again. (<![CDATA[)
  282. """
  283. if self.data:
  284. f.write('<![CDATA[%s]]>' % self.data.replace(']]>',']]>]]><![CDATA['))
  285. class Element(Node):
  286. """ Creates a arbitrary element and is intended to be subclassed not used on its own.
  287. This element is the base of every element it defines a class which resembles
  288. a xml-element. The main advantage of this kind of implementation is that you don't
  289. have to create a toXML method for every different object. Every element
  290. consists of an attribute, optional subelements, optional text and optional cdata.
  291. """
  292. nodeType = Node.ELEMENT_NODE
  293. namespaces = {} # Due to shallow copy this is a static variable
  294. _child_node_types = (Node.ELEMENT_NODE,
  295. Node.PROCESSING_INSTRUCTION_NODE,
  296. Node.COMMENT_NODE,
  297. Node.TEXT_NODE,
  298. Node.CDATA_SECTION_NODE,
  299. Node.ENTITY_REFERENCE_NODE)
  300. def __init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, **args):
  301. if qname is not None:
  302. self.qname = qname
  303. assert(hasattr(self, 'qname'))
  304. self.ownerDocument = None
  305. self.childNodes=[]
  306. self.allowed_children = grammar.allowed_children.get(self.qname)
  307. prefix = self.get_nsprefix(self.qname[0])
  308. self.tagName = prefix + ":" + self.qname[1]
  309. if text is not None:
  310. self.addText(text)
  311. if cdata is not None:
  312. self.addCDATA(cdata)
  313. allowed_attrs = self.allowed_attributes()
  314. if allowed_attrs is not None:
  315. allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
  316. self.attributes={}
  317. # Load the attributes from the 'attributes' argument
  318. if attributes:
  319. for attr, value in attributes.items():
  320. self.setAttribute(attr, value)
  321. # Load the qualified attributes
  322. if qattributes:
  323. for attr, value in qattributes.items():
  324. self.setAttrNS(attr[0], attr[1], value)
  325. if allowed_attrs is not None:
  326. # Load the attributes from the 'args' argument
  327. for arg in args.keys():
  328. self.setAttribute(arg, args[arg])
  329. else:
  330. for arg in args.keys(): # If any attribute is allowed
  331. self.attributes[arg]=args[arg]
  332. if not check_grammar:
  333. return
  334. # Test that all mandatory attributes have been added.
  335. required = grammar.required_attributes.get(self.qname)
  336. if required:
  337. for r in required:
  338. if self.getAttrNS(r[0],r[1]) is None:
  339. raise AttributeError( "Required attribute missing: %s in <%s>" % (r[1].lower().replace('-',''), self.tagName))
  340. def get_knownns(self, prefix):
  341. """ Odfpy maintains a list of known namespaces. In some cases a prefix is used, and
  342. we need to know which namespace it resolves to.
  343. """
  344. global nsdict
  345. for ns,p in nsdict.items():
  346. if p == prefix: return ns
  347. return None
  348. def get_nsprefix(self, namespace):
  349. """ Odfpy maintains a list of known namespaces. In some cases we have a namespace URL,
  350. and needs to look up or assign the prefix for it.
  351. """
  352. if namespace is None: namespace = ""
  353. prefix = _nsassign(namespace)
  354. if not namespace in self.namespaces:
  355. self.namespaces[namespace] = prefix
  356. return prefix
  357. def allowed_attributes(self):
  358. return grammar.allowed_attributes.get(self.qname)
  359. def _setOwnerDoc(self, element):
  360. element.ownerDocument = self.ownerDocument
  361. for child in element.childNodes:
  362. self._setOwnerDoc(child)
  363. def addElement(self, element, check_grammar=True):
  364. """ adds an element to an Element
  365. Element.addElement(Element)
  366. """
  367. if check_grammar and self.allowed_children is not None:
  368. if element.qname not in self.allowed_children:
  369. raise IllegalChild( "<%s> is not allowed in <%s>" % ( element.tagName, self.tagName))
  370. self.appendChild(element)
  371. self._setOwnerDoc(element)
  372. if self.ownerDocument:
  373. self.ownerDocument.rebuild_caches(element)
  374. def addText(self, text, check_grammar=True):
  375. """ Adds text to an element
  376. Setting check_grammar=False turns off grammar checking
  377. """
  378. if check_grammar and self.qname not in grammar.allows_text:
  379. raise IllegalText( "The <%s> element does not allow text" % self.tagName)
  380. else:
  381. if text != '':
  382. self.appendChild(Text(text))
  383. def addCDATA(self, cdata, check_grammar=True):
  384. """ Adds CDATA to an element
  385. Setting check_grammar=False turns off grammar checking
  386. """
  387. if check_grammar and self.qname not in grammar.allows_text:
  388. raise IllegalText( "The <%s> element does not allow text" % self.tagName)
  389. else:
  390. self.appendChild(CDATASection(cdata))
  391. def removeAttribute(self, attr, check_grammar=True):
  392. """ Removes an attribute by name. """
  393. allowed_attrs = self.allowed_attributes()
  394. if allowed_attrs is None:
  395. if type(attr) == type(()):
  396. prefix, localname = attr
  397. self.removeAttrNS(prefix, localname)
  398. else:
  399. raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
  400. else:
  401. # Construct a list of allowed arguments
  402. allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
  403. if check_grammar and attr not in allowed_args:
  404. raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagName))
  405. i = allowed_args.index(attr)
  406. self.removeAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
  407. def setAttribute(self, attr, value, check_grammar=True):
  408. """ Add an attribute to the element
  409. This is sort of a convenience method. All attributes in ODF have
  410. namespaces. The library knows what attributes are legal and then allows
  411. the user to provide the attribute as a keyword argument and the
  412. library will add the correct namespace.
  413. Must overwrite, If attribute already exists.
  414. """
  415. if attr == 'parent' and value is not None:
  416. value.addElement(self)
  417. else:
  418. allowed_attrs = self.allowed_attributes()
  419. if allowed_attrs is None:
  420. if type(attr) == type(()):
  421. prefix, localname = attr
  422. self.setAttrNS(prefix, localname, value)
  423. else:
  424. raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
  425. else:
  426. # Construct a list of allowed arguments
  427. allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
  428. if check_grammar and attr not in allowed_args:
  429. raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagName))
  430. i = allowed_args.index(attr)
  431. self.setAttrNS(allowed_attrs[i][0], allowed_attrs[i][1], value)
  432. def setAttrNS(self, namespace, localpart, value):
  433. """ Add an attribute to the element
  434. In case you need to add an attribute the library doesn't know about
  435. then you must provide the full qualified name
  436. It will not check that the attribute is legal according to the schema.
  437. Must overwrite, If attribute already exists.
  438. """
  439. allowed_attrs = self.allowed_attributes()
  440. prefix = self.get_nsprefix(namespace)
  441. # if allowed_attrs and (namespace, localpart) not in allowed_attrs:
  442. # raise AttributeError( "Attribute %s:%s is not allowed in element <%s>" % ( prefix, localpart, self.tagName))
  443. c = AttrConverters()
  444. self.attributes[(namespace, localpart)] = c.convert((namespace, localpart), value, self)
  445. def getAttrNS(self, namespace, localpart):
  446. """
  447. gets an attribute, given a namespace and a key
  448. @param namespace a unicode string or a bytes: the namespace
  449. @param localpart a unicode string or a bytes:
  450. the key to get the attribute
  451. @return an attribute as a unicode string or a bytes: if both paramters
  452. are byte strings, it will be a bytes; if both attributes are
  453. unicode strings, it will be a unicode string
  454. """
  455. prefix = self.get_nsprefix(namespace)
  456. result = self.attributes.get((namespace, localpart))
  457. assert(
  458. (type(namespace), type(namespace), type(namespace) == \
  459. type(b""), type(b""), type(b"")) or
  460. (type(namespace), type(namespace), type(namespace) == \
  461. type(u""), type(u""), type(u""))
  462. )
  463. return result
  464. def removeAttrNS(self, namespace, localpart):
  465. del self.attributes[(namespace, localpart)]
  466. def getAttribute(self, attr):
  467. """ Get an attribute value. The method knows which namespace the attribute is in
  468. """
  469. allowed_attrs = self.allowed_attributes()
  470. if allowed_attrs is None:
  471. if type(attr) == type(()):
  472. prefix, localname = attr
  473. return self.getAttrNS(prefix, localname)
  474. else:
  475. raise AttributeError( "Unable to get simple attribute - use (namespace, localpart)")
  476. else:
  477. # Construct a list of allowed arguments
  478. allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
  479. i = allowed_args.index(attr)
  480. return self.getAttrNS(allowed_attrs[i][0], allowed_attrs[i][1])
  481. def write_open_tag(self, level, f):
  482. f.write(('<'+self.tagName))
  483. if level == 0:
  484. for namespace, prefix in self.namespaces.items():
  485. f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+'"')
  486. for qname in self.attributes.keys():
  487. prefix = self.get_nsprefix(qname[0])
  488. f.write(u' '+_sanitize(str(prefix+u':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
  489. f.write(u'>')
  490. def write_close_tag(self, level, f):
  491. f.write('</'+self.tagName+'>')
  492. def toXml(self, level, f):
  493. """
  494. Generate an XML stream out of the tree structure
  495. @param level integer: level in the XML tree; zero at root of the tree
  496. @param f an open writable file able to accept unicode strings
  497. """
  498. f.write(u'<'+self.tagName)
  499. if level == 0:
  500. for namespace, prefix in self.namespaces.items():
  501. f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+u'"')
  502. for qname in self.attributes.keys():
  503. prefix = self.get_nsprefix(qname[0])
  504. f.write(u' '+_sanitize(unicode(prefix+':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
  505. if self.childNodes:
  506. f.write(u'>')
  507. for element in self.childNodes:
  508. element.toXml(level+1,f)
  509. f.write(u'</'+self.tagName+'>')
  510. else:
  511. f.write(u'/>')
  512. def _getElementsByObj(self, obj, accumulator):
  513. if self.qname == obj.qname:
  514. accumulator.append(self)
  515. for e in self.childNodes:
  516. if e.nodeType == Node.ELEMENT_NODE:
  517. accumulator = e._getElementsByObj(obj, accumulator)
  518. return accumulator
  519. def getElementsByType(self, element):
  520. """ Gets elements based on the type, which is function from text.py, draw.py etc. """
  521. obj = element(check_grammar=False)
  522. return self._getElementsByObj(obj,[])
  523. def isInstanceOf(self, element):
  524. """ This is a check to see if the object is an instance of a type """
  525. obj = element(check_grammar=False)
  526. return self.qname == obj.qname