"""xml4create.py: For creating XML files from scratch. For full documentation, see: http://www.nmt.edu/tcc/help/pubs/pyxml4/ """ #================================================================ # Imports #---------------------------------------------------------------- import sys import Ft.Xml.Domlette as domlette # - - - - - c l a s s D o c u m e n t T y p e - - - - - class DocumentType: """Represents an XML document type. State/invariants: .rootGI: [ as passed to constructor ] .publicId: [ as passed to constructor ] .systemId: [ as passed to constructor ] """ # - - - D o c u m e n t T y p e . _ _ i n i t _ _ - - - def __init__ ( self, rootGI, publicId=None, systemId=None ): """Constructor for a DocumentType object. [ (rootGI is the root element name) and (publicId is a public ID as a string or None) and (systemId is a system ID as a string) -> return a new DocumentType object with those values ] """ self.rootGI = rootGI self.publicId = publicId self.systemId = systemId # - - - - - c l a s s D o c u m e n t - - - - - class Document: """Represents an XML document. State/invariants: .doctype: [ as passed to constructor, read-only ] .nsMap: [ as passed to constructor, read-only ] .dom: [ the DOMImplementation object ] .node: [ the DOM Document node ] """ # - - - D o c u m e n t . _ _ i n i t _ _ - - - def __init__ ( self, rootGI, doctype=None, nsMap=None ): """Constructor for Document. [ (domlette is Ft.Xml.Domlette) and (rootGI is the root element's qualified name) and (doctype is a DocumentType object or None) -> return a new Document object with a rootGI Element child and document type given by doctype ] """ #-- 1 -- # [ domlette is Ft.Xml.Domlette -> # self.doctype := doctype # self.nsMap := nsMap # self.dom := a DOMImplementation object ] self.doctype = doctype self.nsMap = nsMap self.dom = domlette.implementation #-- 2 -- # [ if rootGI has a namespace prefix that is not a key in # self.nsMap -> # raise KeyError # else if rootGI has a namespace prefix -> # rootNsuri := corresponding namespace URI from # self.nsMap # else -> # rootNsuri := None ] rootNsuri, rootLocal = self.splitQName ( rootGI ) #-- 3 -- # [ self.dom is a DOMImplementation object -> # self.node := a new DOM Document node with default # namespace and root element rootGI ] self.node = self.dom.createDocument ( rootNsuri, rootGI, None ) #-- 4 -- if doctype: self.node.publicId = doctype.publicId self.node.systemId = doctype.systemId #-- 5 -- # [ if rootGI has a namespace prefix that is not a key in # nsMap -> # raise KeyError # else -> # self.root := a new Element with qName rootGI ] self.root = Element ( self, rootGI ) # - - - D o c u m e n t . s p l i t Q N a m e - - - def splitQName ( self, qName ): """Translate a qualified name into (nsURI, localName) [ (self.nsMap is as invariant) and (qName is an XML qualified name) -> if qName has a namespace prefix -> if (self.nsMap is None) or (qName's namespace prefix is not a key in nsMap) -> raise KeyError else -> return (corresponding value from nsMap, qName after the namespace prefix) else -> if None is a key in self.nsMap -> return (None, qName) else -> return (self.nsMap[None], qName) ] """ #-- 1 -- # [ if qName contains ":" -> # prefix := qName up to the first ":" # localName := qName after the first ":" # else if self.nsMap has a key None -> # return (self.nsMap[None], qName) # else -> # return (None, qName) ] firstColon = qName.find(":") if firstColon < 0: if self.nsMap: try: nsuri = self.nsMap[None] except KeyError: nsuri = None else: nsuri = None return (nsuri, qName) else: prefix = qName[:firstColon] localName = qName[firstColon+1:] #-- 2 -- if self.nsMap is None: raise KeyError, ( "Document's root element '%s' had a " "namespace prefix, but no .nsMap was provided." ) #-- 3 -- # [ if prefix is a key in self.nsMap -> # nsURI := the corresponding value # else -> raise KeyError ] nsURI = self.nsMap[prefix] #-- 3 -- return (nsURI, localName) # - - - D o c u m e n t . s e r i a l i z e - - - def serialize ( self, outFile=None ): """Serialize self in XML to outFile. [ (domlette is Ft.Xml.domlette) and (outFile is a writeable file handle, defaulting to sys.stdout) -> outFile +:= a serialized XML representation of self ] """ #-- 1 -- if outFile is None: outFile = sys.stdout #-- 2 -- # [ outFile +:= an XML rendering of self.node ] domlette.Print ( self.node, outFile ) # - - - D o c u m e n t . w r i t e - - - def write ( self, outFile=None ): """Prettyprint self in XML to outFile. [ (domlette is Ft.Xml.domlette) and (outFile is a writeable file handle, defaulting to sys.stdout) -> outFile +:= a prettyprinted XML representation of self ] """ #-- 1 -- if outFile is None: outFile = sys.stdout #-- 2 -- # [ outFile +:= an XML rendering of self.node ] domlette.PrettyPrint ( self.node, outFile ) # - - - - - c l a s s E l e m e n t - - - - - class Element: """Represents one XML element. State/Invariants: .node: [ a DOM Element node containing the actual element ] .doc: [ the Document element rooting the tree containing the parent ] """ # - - - E l e m e n t . _ _ i n i t _ _ - - - def __init__ ( self, parent, gi, **attrs ): """Constructor for Element. [ parent is an Element or DocumentFragment object -> parent := parent with a new XML element appended as its last or only child, having name=gi and attributes=attrs return that new Element parent is a Document object -> parent := parent with its .root attribute set to a new Element object wrapping parent.documentElement, and having attributes=attrs ] """ #-- 1 -- # [ if parent is a Document -> # self.node := parent.documentElement # else if gi is a valid qName in the context of # self.doc.nsMap -> # parent := parent with a new XML element # appended as its last or only child, having # name=qName and attributes=attr # self.node := a new DOM Element object having # the nsURI and localName inferred from # self.doc.nsMap # else -> raise KeyError ] if parent.__class__ is Document: #-- 1.1 -- # [ parent is a Document -> # self.node := parent.node.documentElement with # attributes from attrs attached # self.doc := parent ] self.__wrapRoot ( parent, **attrs ) else: #-- 1.2 -- # [ if gi is a valid qName in the context of # self.doc.nsMap -> # parent := parent with a new XML element # appended as its last or only child, having # name=gi and attributes from attrs # self.node := a new DOM Element object having # the nsURI and localName inferred from # self.doc.nsMap # self.doc := parent.doc # else -> raise KeyError ] self.__newElement ( parent, gi, **attrs ) # - - - E l e m e n t . _ _ w r a p R o o t - - - def __wrapRoot ( self, parent, **attrs ): """Set up an Element wrapping the document root element. [ (parent is a Document) and (attrs is a dictionary of attribute names and values) -> self.node := parent.node.documentElement with attributes from attrs attached self.doc := parent ] """ #-- 1 -- self.node = parent.node.documentElement self.doc = parent #-- 2 -- # [ if attrs is a dictionary -> # self.node := self.node with attribute names # (with trailing "_" removed if present) and # corresponding values from attrs # else -> I ] if attrs: self.update ( attrs ) # - - - E l e m e n t . _ _ s e t A t t r - - - def __setAttr ( self, name, value ): """Store one XML attribute in a DOM Element. [ name and value are strings -> if name has a namespace prefix not defined in self.doc.nsMap -> raise KeyError else if name has a namespace prefix defined in self.doc.nsMap -> self.node := self.node with a new Attribute added having nsURI=self.doc.nsMap[attrName], localName=(attrName past the first colon, trailing underbar dropped if any), and value=value else -> self.node := self.node with a new Attribute added having nsURI=None, localName=(attrName, trailing underbar dropped if any), and value=value ] """ #-- 1 -- # [ if name has a namespace prefix -> # if that prefix is not a key in self.doc.nsMap -> # raise KeyError # else -> # nsUri := the corresponding value # localName := name after the colon # else -> # nsUri := None # localName := name ] nsUri, localName = self.doc.splitQName ( name ) #-- 2 -- if name[-1] == '_': name = name[:-1] #-- 3 -- # [ self.node := self.node with a new attribute added # having nsURI=nsUri, name=name, and value=value ] self.node.setAttributeNS ( nsUri, name, value ) # - - - E l e m e n t . _ _ n e w E l e m e n t - - - def __newElement ( self, parent, gi, **attrs ): """Create an element child of an existing element. [ (parent is an Element) and (gi is a string) and (attrs is a dictionary or None) -> if gi is a valid qName in the context of self.doc.nsMap -> parent := parent with a new XML element appended as its last or only child, having name=gi and attributes from attrs self.node := a new DOM Element object having the nsURI and localName inferred from self.doc.nsMap self.doc := parent.doc else -> raise KeyError ] """ #-- 1 -- self.doc = parent.doc #-- 2 -- # [ if gi is a qualified name in the context of # self.doc.nsMap -> # nsUri := the namespace URI of gi in that context # localName := the local name of gi # else -> raise KeyError ] nsUri, localName = self.doc.splitQName ( gi ) #-- 3 -- # [ self.node := a DOM Element node with namespace # URI=nsUri and name=name ] self.node = self.doc.node.createElementNS ( nsUri, gi ) #-- 4 -- # [ parent.node := parent.node with self.node added as # its last or only child ] parent.node.appendChild ( self.node ) #-- 5 -- # [ if attrs is None -> # I # else if attrs contains any namespace prefixes that are # not keys in self.doc.nsMap -> # raise KeyError # else -> # self.node := self.node with attribute names # (with trailing "_" removed if present) and # corresponding values from attrs ] if attrs: self.update ( attrs ) # - - - E l e m e n t . _ _ s e t i t e m _ _ - - - def __setitem__ ( self, key, value ): """Add an attribute to self. [ key is an attribute name as a string -> if key has namespace prefix not defined in self.doc.nsMap -> raise KeyError else -> self := self with its attribute (key), minus any trailing underbar if present, set to (value) ] """ self.__setAttr ( key, value ) # - - - E l e m e n t . u p d a t e - - - def update ( self, attrs ): """Add supplied attributes to the XML Element. [ (self.doc is the containing Document) and (attrs is a dictionary -> if attrs contains any namespace prefixes that are not keys in self.doc.nsMap -> raise KeyError else -> self.node := self.node with attribute names (with trailing "_" removed if present) and corresponding values from attrs ] """ #-- 1 -- for attrName in attrs: #-- 1 body -- # [ attrName is a key in attrs -> # if attrName has a namespace prefix not defined # in self.doc.nsMap -> # raise KeyError # else if attrName has a namespace prefix defined # in self.doc.nsMap -> # self.node := self.node with a new Attribute # added having nsURI=self.doc.nsMap[attrName], # localName=(attrName past the first # colon, trailing underbar dropped if any), # and value=attrs[attrName] # else -> # self.node := self.node with a new Attribute # added having nsURI=None and # localName=(attrName, trailing underbar # dropped if any), and value=attrs[attrName] ] self.__setAttr ( attrName, attrs[attrName] ) # - - - - - c l a s s T e x t - - - - - class Text: """Represents a text node. State/Invariants: .node: [ a DOM Text node holding the text content ] """ # - - - T e x t . _ _ i n i t _ _ - - - def __init__ ( self, parent, content ): """Constructor for Text [ parent is an Element object -> parent := parent with a new text node added with text=content return a new Text object representing that text ] """ #-- 1 -- # [ parent is an Element -> # self.node := a new DOM Text node with content=content ] self.node = parent.doc.node.createTextNode ( content ) #-- 2 -- # [ parent := parent with self.node added as its next or # only child ] parent.node.appendChild ( self.node ) # - - - - - c l a s s C o m m e n t - - - - - class Comment: """Represents an XML comment.""" # - - - C o m m e n t . _ _ i n i t _ _ - - - def __init__ ( self, parent, content ): """Constructor for a Comment object. [ parent is an Element object -> parent := parent with a new comment node added with text=content return a new Comment object representing that node ] """ #-- 1 -- # [ parent is an Element -> # self.node := a new DOM Comment node with content=content ] self.node = parent.doc.node.createComment ( content ) #-- 2 -- # [ parent := parent with self.node added as its last or # only child ] parent.node.appendChild ( self.node ) # - - - - - c l a s s D o c u m e n t F r a g m e n t - - - - - class DocumentFragment: """Represents part of an XML document. State/Invariants: .doc: [ a Document object ] .node: [ a DOM DocumentFragment object whose .ownerDocument attribute contains a DOM Document instance ] """ # - - - D o c u m e n t F r a g m e n t . _ _ i n i t _ _ - - - def __init__ ( self, nsMap=None ): """Constructor for DocumentFragment. """ #-- 1 -- # [ dom := a DOMImplementation object ] dom = domlette.implementation #-- 2 -- # [ doc := a new, empty DOM Document object ] self.doc = Document('dummy', nsMap=nsMap) #-- 3 -- # [ doc is a DOM Document object -> # self.node := a DOM DocumentFragment object ] self.node = self.doc.node.createDocumentFragment() # - - - D o c u m e n t F r a g m e n t . s e r i a l i z e - - - def serialize ( self, outFile=None ): """Serialize self in XML to outFile.""" if outFile is None: outFile = sys.stdout domlette.Print ( self.node, outFile ) # - - - D o c u m e n t F r a g m e n t . w r i t e - - - def write ( self, outFile=None ): """Prettyprint self in XML to outFile.""" if outFile is None: outFile = sys.stdout domlette.PrettyPrint ( self.node, outFile )