diff --git a/pisi/xml/autoxml.py b/pisi/xml/autoxml.py index 7b74d6e5..d1bc58dc 100644 --- a/pisi/xml/autoxml.py +++ b/pisi/xml/autoxml.py @@ -101,7 +101,7 @@ class LocalText(dict): assert self.tag != '' for key in self.iterkeys(): newnode = newNode(node, self.tag) - newnode.setAttribute('xml:lang', key) + newnode.setAttributeNS(None, 'xml:lang', key) newtext = newTextNode(node, self[key]) newnode.appendChild(newtext) node.appendChild(newnode) @@ -400,7 +400,7 @@ class autoxml(oo.autosuper, oo.autoprop): errs.append(_("autoxml.write: object validation has failed")) raise Error(*errs) errs = [] - self.newDOM() + self.newDocument() self.encode(self.rootNode(), errs) if hasattr(self, 'write_hook'): self.write_hook(errs) @@ -423,7 +423,7 @@ class autoxml(oo.autosuper, oo.autoprop): return getNodeAttribute(node, attr) def writetext(node, attr, text): #print 'write attr', attr, text - node.setAttribute(attr, text) + node.setAttributeNS(None, attr, text) anonfuns = cls.gen_anon_basic(attr, spec, readtext, writetext) return cls.gen_named_comp(attr, spec, anonfuns) @@ -637,7 +637,7 @@ class autoxml(oo.autosuper, oo.autoprop): if node and obj: try: #FIXME: this doesn't look pretty - classnode = node.ownerDocument.createElement(tag) + classnode = newNode(node, tag) obj.encode(classnode, errs) node.appendChild(classnode) except Error: @@ -684,7 +684,7 @@ class autoxml(oo.autosuper, oo.autoprop): errs.append(where + _('Mandatory list empty')) ix = 1 for node in nodes: - dummy = node.ownerDocument.createElement("Dummy") + dummy = newNode(node, "Dummy") dummy.appendChild(node) l.append(decode_item(dummy, errs, where + unicode("[%s]" % ix))) #l.append(decode_item(node, errs, where + unicode("[%s]" % ix))) diff --git a/pisi/xml/xmlext.py b/pisi/xml/xmlext.py index e7133ba7..cfc0cde8 100644 --- a/pisi/xml/xmlext.py +++ b/pisi/xml/xmlext.py @@ -15,5 +15,8 @@ try: zimbabwe # comment out to disable piks from xmlextpiks import * except: - print 'xmlext: piksemel implementation cannot be loaded, falling back to minidom' - from xmlextmdom import * + try: + from xmlextcdom import * + except: + print 'xmlext: cDomlette/piksemel implementation cannot be loaded, falling back to minidom' + from xmlextmdom import * diff --git a/pisi/xml/xmlextcdom.py b/pisi/xml/xmlextcdom.py new file mode 100644 index 00000000..15219b49 --- /dev/null +++ b/pisi/xml/xmlextcdom.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005, TUBITAK/UEKAE +# +# 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. +# +# Please read the COPYING file. +# +# Authors: Eray Ozkural +# Baris Metin +# Faik Uygur + +""" + xmlext is a helper module for accessing XML files using + xml.dom.minidom . It is a convenient wrapper for some + DOM functions, and provides path based get/add functions + as in KDE API. + + function names are mixedCase for compatibility with minidom, + an 'old library' + + this implementation uses cDomlette from 4suite.org + adapted from the minidom implementation by Faik Uygur. +""" + +import gettext +__trans = gettext.translation('pisi', fallback=True) +_ = __trans.ugettext + +import Ft +from Ft.Xml.Domlette import implementation +from Ft.Xml import ParsePath +from Ft.Xml.Domlette import NonvalidatingReader + +import pisi + +class XmlError(pisi.Error): + "named this way because the class if mostly used with an import *" + pass + +# Document wrappers + +def newDocument(tag): + # create a document and return document root + doc = implementation.createDocument(None, tag, None) + return doc.documentElement + +def parse(filename): + # parse file and return document root + try: + doc = ParsePath(filename) + return doc.documentElement + except Ft.FtException, e: + raise Error(_("File '%s' has invalid XML: %s") % (filename, str(e))) + +def newNode(node, tag): + return node.ownerDocument.createElementNS(None, tag) + +def newTextNode(node, text): + return node.ownerDocument.createTextNode(text) + +# Node related wrappers + +def getNodeAttribute(node, attrname): + """get named attribute from DOM node""" + if not node.hasAttributeNS(None, attrname): + return None + else: + return node.getAttributeNS(None, attrname) + +def getChildElts(node): + """get only child elements""" + return filter(lambda x:x.nodeType == x.ELEMENT_NODE, node.childNodes) + +def getTagByName(parent, childName): + return [x for x in parent.childNodes + if x.nodeType == x.ELEMENT_NODE and x.tagName == childName] + +def getNodeText(node, tagpath = ""): + """get the first child and expect it to be text!""" + if tagpath!="": + node = getNode(node, tagpath) + try: + child = node.childNodes[0] + except IndexError: + return None + except AttributeError: # no node by that name + return None + if child.nodeType == child.TEXT_NODE: + # in any case, strip whitespaces... + return child.data.strip() + else: + raise XmlError(_("getNodeText: Expected text node, got something else!")) + +def getChildText(node_s, tagpath): + """get the text of a child at the end of a tag path""" + node = getNode(node_s, tagpath) + if not node: + return None + return getNodeText(node) + +def getNode(node, tagpath): + """returns the *first* matching node for given tag path.""" + + assert type(tagpath)==str + tags = tagpath.split('/') + assert len(tags)>0 + + # iterative code to search for the path + for tag in tags: + currentNode = None + for child in node.childNodes: + if child.nodeType == node.ELEMENT_NODE and child.tagName == tag: + currentNode = child + break + if not currentNode: + return None + else: + node = currentNode + return currentNode + +def getAllNodes(node, tagPath): + """retrieve all nodes that match a given tag path.""" + + #FIXME: better tag split + tags = tagPath.split('/') + + if len(tags) == 0: + return [] + + nodeList = [node] # basis case + + for tag in tags: + results = map(lambda x: getTagByName(x, tag), nodeList) + nodeList = [] + for x in results: + nodeList.extend(x) + pass # emacs indentation error, keep it here + + if len(nodeList) == 0: + return [] + + return nodeList + +def createTagPath(node, tags): + """create new child at the end of a tag chain starting from node + no matter what""" + if len(tags)==0: + return node + dom = node.ownerDocument + for tag in tags: + node = node.appendChild(dom.createElementNS(None, tag)) + return node + +def addTagPath(node, tags, newnode=None): + """add newnode at the end of a tag chain, smart one""" + node = createTagPath(node, tags) + if newnode: # node to add specified + node.appendChild(newnode) + return node + +def addNode(node, tagpath, newnode = None, branch=True): + """add a new node at the end of the tree and returns it + if newnode is given adds that node, too.""" + + assert type(tagpath)==str + tags = [] + if tagpath != "": + tags = tagpath.split('/') # tag chain + else: + addTagPath(node, [], newnode) + return node #FIXME: is this correct!?!? + + assert len(tags)>0 # we want a chain + + # iterative code to search for the path + + if branch: + rem = 1 + else: + rem = 0 + + while len(tags) > rem: + tag = tags.pop(0) + nodeList = getTagByName(node, tag) + if len(nodeList) == 0: # couldn't find + tags.insert(0, tag) # put it back in + return addTagPath(node, tags, newnode) + else: + node = nodeList[len(nodeList)-1] # discard other matches + else: + # had only one tag.. + return addTagPath(node, tags, newnode) + + return node + +def addText(node, tagPath, text, branch = True): + newnode = newTextNode(node, text) + return addNode(node, tagPath, newnode, branch = branch) diff --git a/pisi/xml/xmlfile.py b/pisi/xml/xmlfile.py index bb58a4c5..529018c9 100644 --- a/pisi/xml/xmlfile.py +++ b/pisi/xml/xmlfile.py @@ -13,8 +13,10 @@ try: horagata # comment out to disable piks - from xmlextpiks import * - from xmlfilepiks import * + #from xmlfilepiks import * except: - print 'xmlfile: piksemel implementation cannot be loaded, falling back to minidom' - from xmlfilemdom import * + try: + from xmlfilecdom import * + except: + print 'xmlext: cDomlette/piksemel implementation cannot be loaded, falling back to minidom' + from xmlfilemdom import * diff --git a/pisi/xml/xmlfilecdom.py b/pisi/xml/xmlfilecdom.py new file mode 100644 index 00000000..aca65eed --- /dev/null +++ b/pisi/xml/xmlfilecdom.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005, TUBITAK/UEKAE +# +# 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. +# +# Please read the COPYING file. +# +# Authors: Eray Ozkural +# Baris Metin + +""" + XmlFile class further abstracts a dom object using the + high-level dom functions provided in xmlext module (and sorely lacking + in xml.dom :( ) + + function names are mixedCase for compatibility with minidom, + an 'old library' + + this implementation uses cDomlette from 4suite.org + adapted from the minidom implementation by Faik Uygur. +""" + +import gettext +__trans = gettext.translation('pisi', fallback=True) +_ = __trans.ugettext + +import codecs + +import Ft +from Ft.Xml.Domlette import implementation +from Ft.Xml.Domlette import NonvalidatingReader +from Ft.Xml import Parse, ParsePath +from Ft.Xml.Domlette import Print, PrettyPrint + +import pisi + +class Error(pisi.Error): + "named this way because the class if mostly used with an import *" + pass + +class XmlFile(object): + """A class to help reading and writing an XML file""" + + def __init__(self, tag): + self.rootTag = tag + self.newDocument() + + def newDocument(self): + """clear DOM""" + self.doc = implementation.createDocument(None, self.rootTag, None) + + def unlink(self): + """deallocate DOM structure""" + #del self.doc + self.doc = None # why? + + def rootNode(self): + """returns root document element""" + return self.doc.documentElement + + def readxml(self, path): + try: + self.doc = ParsePath(path) + return self.doc.documentElement + except Ft.FtException, e: + raise Error(_("File '%s' has invalid XML:") % (path, str(e)) ) + + def writexml(self, path): + f = file(path, 'w') + Print(self.rootNode(), stream = f) + f.close() + + def verifyRootTag(self): + actual_roottag = self.rootNode().tagName + if actual_roottag != self.rootTag: + raise Error(_("Root tagname %s not identical to %s as expected") % + (actual_roottag, self.rootTag) ) + + # construction helpers + + def newNode(self, tag): + return self.dom.createElementNS(None, tag) + + def newTextNode(self, text): + return self.dom.createTextNode(text) + + def newAttribute(self, attr): + return self.dom.createAttribute(attr) + + # read helpers + + def getNode(self, tagPath = ""): + """returns the *first* matching node for given tag path.""" + self.verifyRootTag() + return getNode(self.dom.documentElement, tagPath) + + def getNodeText(self, tagPath): + """returns the text of *first* matching node for given tag path.""" + node = self.getNode(tagPath) + if not node: + return None + return getNodeText(node) + + def getAllNodes(self, tagPath): + """returns all nodes matching a given tag path.""" + self.verifyRootTag() + return getAllNodes(self.dom.documentElement, tagPath) + + def getChildren(self, tagpath): + """ returns the children of the given path""" + node = self.getNode(tagpath) + return node.childNodes + + # get only elements of a given type + #FIXME: this doesn't work + def getChildrenWithType(self, tagpath, type): + """ returns the children of the given path, only with given type """ + node = self.getNode(tagpath) + return filter(lambda x:x.nodeType == type, node.childNodes) + + # get only child elements + def getChildElts(self, tagpath): + """ returns the children of the given path, only with given type """ + node = self.getNode(tagpath) + try: + return filter(lambda x:x.nodeType == x.ELEMENT_NODE, + node.childNodes) + except AttributeError: + return None + + # write helpers + + def addNode(self, tagPath, newnode = None): + "this adds the newnode under given tag path" + self.verifyRootTag() + return addNode(self.dom.documentElement, tagPath, newnode) + + def addNodeUnder(self, node, tagPath, newnode = None): + "this adds the new stuff under node and then following tag path" + self.verifyRootTag() + return addNode(node, tagPath, newnode) + + def addChild(self, newnode): + "add a new child node right under root element document" + self.dom.documentElement.appendChild(newnode) + + def addText(self, node, text): + "add text to node" + node.appendChild(self.newTextNode(text)) + + def addTextNode(self, tagPath, text): + "add a text node with given tag path" + node = self.addNode(tagPath, self.newTextNode(text)) + return node + + def addTextNodeUnder(self, node, tagPath, text): + "add a text node under given node with tag path (phew)" + return self.addNodeUnder(node, tagPath, self.newTextNode(text)) diff --git a/pisi/xml/xmlfilemdom.py b/pisi/xml/xmlfilemdom.py index 322c3ced..1afd374f 100644 --- a/pisi/xml/xmlfilemdom.py +++ b/pisi/xml/xmlfilemdom.py @@ -43,9 +43,9 @@ class XmlFile(object): def __init__(self, tag): self.rootTag = tag - self.newDOM() + self.newDocument() - def newDOM(self): + def newDocument(self): """clear DOM""" impl = mdom.getDOMImplementation() self.dom = impl.createDocument(None, self.rootTag, None) diff --git a/pisi/xml/xmlfilepiks.py b/pisi/xml/xmlfilepiks.py index 721c1e90..19df6264 100644 --- a/pisi/xml/xmlfilepiks.py +++ b/pisi/xml/xmlfilepiks.py @@ -36,9 +36,9 @@ class XmlFile(object): def __init__(self, tag): self.rootTag = tag - self.newDOM() + self.newDocument() - def newDOM(self): + def newDocument(self): """clear DOM""" self.doc = newDocument(self.rootTag)