Namespace handling fixes
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
index c66b770..d5c4ac0 100644 (file)
@@ -39,12 +39,8 @@ using namespace std;
 \r
 #define XT_log (*static_cast<Category*>(m_log))\r
 \r
-AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller(const XMLCh* targetNamespaceURI, const XMLCh* targetLocalName)\r
-        : m_targetQName(targetNamespaceURI, targetLocalName),\r
-        m_log(&Category::getInstance(XMLTOOLING_LOGCAT".Marshaller")) {\r
-    if (!targetLocalName || !*targetLocalName)\r
-        throw MarshallingException("targetLocalName cannot be null or empty");\r
-}\r
+AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller()\r
+    : m_log(&Category::getInstance(XMLTOOLING_LOGCAT".Marshaller")) {}\r
 \r
 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocument* document) const\r
 {\r
@@ -62,7 +58,8 @@ DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocum
         if (cachedDOM) {\r
             if (!document || document==cachedDOM->getOwnerDocument()) {\r
                 XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
-                setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
+                if (document)\r
+                    setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
                 dc->releaseParentDOM(true);\r
                 return cachedDOM;\r
             }\r
@@ -91,7 +88,7 @@ DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocum
             xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
             );\r
         setDocumentElement(document, domElement);\r
-        marshallInto(xmlObject, domElement);\r
+        marshallInto(*xmlObject, domElement);\r
 \r
         //Recache the DOM.\r
         if (dc) {\r
@@ -150,7 +147,7 @@ DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMEleme
         xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
         );\r
     parentElement->appendChild(domElement);\r
-    marshallInto(xmlObject, domElement);\r
+    marshallInto(*xmlObject, domElement);\r
 \r
     //Recache the DOM.\r
     if (dc) {\r
@@ -162,14 +159,15 @@ DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMEleme
     return domElement;\r
 }\r
         \r
-void AbstractXMLObjectMarshaller::marshallInto(XMLObject* xmlObject, DOMElement* targetElement) const\r
+void AbstractXMLObjectMarshaller::marshallInto(XMLObject& xmlObject, DOMElement* targetElement) const\r
 {\r
-    targetElement->setPrefix(xmlObject->getElementQName().getPrefix());\r
+    if (xmlObject.getElementQName().hasPrefix())\r
+        targetElement->setPrefix(xmlObject.getElementQName().getPrefix());\r
+    marshallElementType(xmlObject, targetElement);\r
     marshallNamespaces(xmlObject, targetElement);\r
     marshallAttributes(xmlObject, targetElement);\r
     marshallChildElements(xmlObject, targetElement);\r
     marshallElementContent(xmlObject, targetElement);\r
-    marshallElementType(xmlObject, targetElement);\r
 \r
     /* TODO Signing/Encryption\r
     if (xmlObject instanceof SignableXMLObject) {\r
@@ -182,9 +180,9 @@ void AbstractXMLObjectMarshaller::marshallInto(XMLObject* xmlObject, DOMElement*
     */\r
 }\r
 \r
-void AbstractXMLObjectMarshaller::marshallElementType(XMLObject* xmlObject, DOMElement* domElement) const\r
+void AbstractXMLObjectMarshaller::marshallElementType(XMLObject& xmlObject, DOMElement* domElement) const\r
 {\r
-    const QName* type = xmlObject->getSchemaType();\r
+    const QName* type = xmlObject.getSchemaType();\r
     if (type) {\r
         XT_log.debug("setting xsi:type attribute for XMLObject");\r
         \r
@@ -212,7 +210,7 @@ void AbstractXMLObjectMarshaller::marshallElementType(XMLObject* xmlObject, DOME
             XMLString::release(&xsivalue);\r
 \r
         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
-        xmlObject->addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
+        xmlObject.addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
     }\r
 }\r
 \r
@@ -221,6 +219,14 @@ public:
     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
         const XMLCh* prefix=ns.getNamespacePrefix();\r
         const XMLCh* uri=ns.getNamespaceURI();\r
+        \r
+        // Check to see if the prefix is already declared properly above this node.\r
+        if (!ns.alwaysDeclare()) {\r
+            const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);\r
+            if (declared && XMLString::equals(declared,uri))\r
+                return;\r
+        }\r
+            \r
         if (prefix && *prefix) {\r
             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
             *xmlns=chNull;\r
@@ -234,12 +240,42 @@ public:
             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
         }\r
     }\r
+\r
+    const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {\r
+        // Return NULL if no declaration in effect. The empty string signifies the null namespace.\r
+        if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {\r
+            // At the root, the default namespace is set to the null namespace.\r
+            if (!prefix || !*prefix)\r
+                return &chNull;\r
+            return NULL;    // we're done\r
+        }\r
+        DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();\r
+        if (!attributes)\r
+            return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent\r
+        DOMNode* childNode;\r
+        DOMAttr* attribute;\r
+        for (XMLSize_t i=0; i<attributes->getLength(); i++) {\r
+            childNode = attributes->item(i);\r
+            if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?\r
+                continue;\r
+            attribute = static_cast<DOMAttr*>(childNode);\r
+            if (!XMLString::equals(attribute->getNamespaceURI(),XMLConstants::XMLNS_NS))\r
+                continue;   // not a namespace declaration\r
+            // Local name should be the prefix and the value would be the URI, except for the default namespace.\r
+            if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX))\r
+                return attribute->getNodeValue();\r
+            else if (XMLString::equals(prefix,attribute->getLocalName()))\r
+                return attribute->getNodeValue();\r
+        }\r
+        // Defer to parent.\r
+        return lookupNamespaceURI(n->getParentNode(),prefix);\r
+    }\r
 };\r
 \r
-void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject* xmlObject, DOMElement* domElement) const\r
+void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject& xmlObject, DOMElement* domElement) const\r
 {\r
     XT_log.debug("marshalling namespace attributes for XMLObject");\r
-    const set<Namespace>& namespaces = xmlObject->getNamespaces();\r
+    const set<Namespace>& namespaces = xmlObject.getNamespaces();\r
     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
 }\r
 \r
@@ -248,6 +284,8 @@ class _marshallchild : public binary_function<XMLObject*,DOMElement*,void> {
 public:\r
     _marshallchild(void* log) : m_log(log) {}\r
     void operator()(XMLObject* obj, DOMElement* element) const {\r
+        if (!obj)\r
+            return;\r
         if (XT_log.isDebugEnabled()) {\r
             XT_log.debug("getting marshaller for child XMLObject: %s", obj->getElementQName().toString().c_str());\r
         }\r
@@ -260,16 +298,14 @@ public:
                 );\r
             throw MarshallingException("Marshaller found unknown child element, but no default marshaller was found.");\r
         }\r
-        element->appendChild(marshaller->marshall(obj, element->getOwnerDocument()));\r
+        element->appendChild(marshaller->marshall(obj, element));\r
     }\r
 };\r
 \r
-void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject* xmlObject, DOMElement* domElement) const\r
+void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject& xmlObject, DOMElement* domElement) const\r
 {\r
     XT_log.debug("marshalling child elements for XMLObject");\r
 \r
-    vector<XMLObject*> children;\r
-    if (xmlObject->getOrderedChildren(children)) {\r
-        for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
-    }\r
+    const list<XMLObject*>& children=xmlObject.getOrderedChildren();\r
+    for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
 }\r