Merge branch '1.x' of ssh://authdev.it.ohio-state.edu/~scantor/git/cpp-xmltooling...
[shibboleth/cpp-xmltooling.git] / xmltooling / util / XMLHelper.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * XMLHelper.cpp
23  * 
24  * A helper class for working with W3C DOM objects. 
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "QName.h"
30 #include "XMLObject.h"
31 #include "util/XMLHelper.h"
32 #include "util/XMLConstants.h"
33
34 #include <xercesc/framework/MemBufFormatTarget.hpp>
35 #include <xercesc/util/XMLUniDefs.hpp>
36
37 using namespace xmltooling;
38 using namespace xercesc;
39 using namespace std;
40
41 static const XMLCh type[]={chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull };
42     
43 bool XMLHelper::hasXSIType(const DOMElement* e)
44 {
45     return (e && e->hasAttributeNS(xmlconstants::XSI_NS, type));
46 }
47
48 xmltooling::QName* XMLHelper::getXSIType(const DOMElement* e)
49 {
50     DOMAttr* attribute = e->getAttributeNodeNS(xmlconstants::XSI_NS, type);
51     if (attribute) {
52         const XMLCh* attributeValue = attribute->getTextContent();
53         if (attributeValue && *attributeValue) {
54             int i;
55             if ((i=XMLString::indexOf(attributeValue,chColon))>0) {
56                 XMLCh* prefix=new XMLCh[i+1];
57                 XMLString::subString(prefix,attributeValue,0,i);
58                 prefix[i]=chNull;
59                 xmltooling::QName* ret=new xmltooling::QName(e->lookupNamespaceURI(prefix), attributeValue + i + 1, prefix);
60                 delete[] prefix;
61                 return ret;
62             }
63             else {
64                 return new xmltooling::QName(e->lookupNamespaceURI(nullptr), attributeValue);
65             }
66         }
67     }
68
69     return nullptr;
70 }
71
72 DOMAttr* XMLHelper::getIdAttribute(const DOMElement* domElement)
73 {
74     if(!domElement->hasAttributes()) {
75         return nullptr;
76     }
77     
78     DOMNamedNodeMap* attributes = domElement->getAttributes();
79     DOMAttr* attribute;
80     for(XMLSize_t i = 0; i < attributes->getLength(); i++) {
81         attribute = static_cast<DOMAttr*>(attributes->item(i));
82         if(attribute->isId()) {
83             return attribute;
84         }
85     }
86     
87     return nullptr;
88 }
89
90 const XMLObject* XMLHelper::getXMLObjectById(const XMLObject& tree, const XMLCh* id)
91 {
92     if (XMLString::equals(id, tree.getXMLID()))
93         return &tree;
94     
95     const XMLObject* ret;
96     const list<XMLObject*>& children = tree.getOrderedChildren();
97     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
98         if (*i) {
99             ret = getXMLObjectById(*(*i), id);
100             if (ret)
101                 return ret;
102         }
103     }
104     
105     return nullptr;
106 }
107
108 XMLObject* XMLHelper::getXMLObjectById(XMLObject& tree, const XMLCh* id)
109 {
110     if (XMLString::equals(id, tree.getXMLID()))
111         return &tree;
112     
113     XMLObject* ret;
114     const list<XMLObject*>& children = tree.getOrderedChildren();
115     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
116         if (*i) {
117             ret = getXMLObjectById(*(*i), id);
118             if (ret)
119                 return ret;
120         }
121     }
122     
123     return nullptr;
124 }
125
126 void XMLHelper::getNonVisiblyUsedPrefixes(const XMLObject& tree, map<xstring,xstring>& prefixes)
127 {
128     map<xstring,xstring> child_prefixes;
129     const list<XMLObject*>& children = tree.getOrderedChildren();
130     for (list<XMLObject*>::const_iterator i = children.begin(); i != children.end(); ++i) {
131         if (*i)
132             getNonVisiblyUsedPrefixes(*(*i), child_prefixes);
133     }
134     const set<Namespace>& nsset = tree.getNamespaces();
135     for (set<Namespace>::const_iterator ns = nsset.begin(); ns != nsset.end(); ++ns) {
136         // Check for xmlns:xml.
137         if (XMLString::equals(ns->getNamespacePrefix(), xmlconstants::XML_PREFIX) && XMLString::equals(ns->getNamespaceURI(), xmlconstants::XML_NS))
138             continue;
139         switch (ns->usage()) {
140             case Namespace::Indeterminate:
141                 break;
142             case Namespace::VisiblyUsed:
143             {
144                 // See if the prefix was noted as non-visible below.
145                 const XMLCh* p = ns->getNamespacePrefix() ? ns->getNamespacePrefix() : &chNull;
146                 map<xstring,xstring>::iterator decl = child_prefixes.find(p);
147                 if (decl != child_prefixes.end()) {
148                     // It's declared below, see if it's the same namespace. If so, pull it from the set,
149                     // otherwise leave it in the set.
150                     if (decl->second == (ns->getNamespaceURI() ? ns->getNamespaceURI() : &chNull))
151                         child_prefixes.erase(decl);
152                 }
153                 break;
154             }
155             case Namespace::NonVisiblyUsed:
156                 // It may already be in the map from another branch of the tree, but as long
157                 // as it's set to something so the parent knows about it, we're good.
158                 prefixes[ns->getNamespacePrefix() ? ns->getNamespacePrefix() : &chNull] = (ns->getNamespaceURI() ? ns->getNamespaceURI() : &chNull);
159                 break;
160         }
161     }
162
163     prefixes.insert(child_prefixes.begin(), child_prefixes.end());
164 }
165
166 xmltooling::QName* XMLHelper::getNodeQName(const DOMNode* domNode)
167 {
168     if (domNode)
169         return new xmltooling::QName(domNode->getNamespaceURI(), domNode->getLocalName(), domNode->getPrefix());
170     return nullptr; 
171 }
172
173 xmltooling::QName* XMLHelper::getAttributeValueAsQName(const DOMAttr* attribute)
174 {
175     return getNodeValueAsQName(attribute);
176 }
177
178 xmltooling::QName* XMLHelper::getNodeValueAsQName(const DOMNode* domNode)
179 {
180     if (!domNode)
181         return nullptr;
182     
183     const XMLCh* value=domNode->getTextContent();
184     if (!value || !*value)
185         return nullptr;
186
187     int i;
188     if ((i=XMLString::indexOf(value,chColon))>0) {
189         XMLCh* prefix=new XMLCh[i+1];
190         XMLString::subString(prefix,value,0,i);
191         prefix[i]=chNull;
192         xmltooling::QName* ret=new xmltooling::QName(domNode->lookupNamespaceURI(prefix), value + i + 1, prefix);
193         delete[] prefix;
194         return ret;
195     }
196     
197     return new xmltooling::QName(domNode->lookupNamespaceURI(nullptr), value);
198 }
199
200 bool XMLHelper::getNodeValueAsBool(const xercesc::DOMNode* domNode, bool def)
201 {
202     if (!domNode)
203         return def;
204     const XMLCh* value = domNode->getNodeValue();
205     if (!value || !*value)
206         return def;
207     if (*value == chLatin_t || *value == chDigit_1)
208         return true;
209     else if (*value == chLatin_f || *value == chDigit_0)
210         return false;
211     return def;
212 }
213
214 DOMElement* XMLHelper::appendChildElement(DOMElement* parentElement, DOMElement* childElement)
215 {
216     DOMDocument* parentDocument = parentElement->getOwnerDocument();
217     if (childElement->getOwnerDocument() != parentDocument) {
218         childElement = static_cast<DOMElement*>(parentDocument->importNode(childElement, true));
219     }
220
221     parentElement->appendChild(childElement);
222     return childElement;
223 }
224
225 bool XMLHelper::isNodeNamed(const xercesc::DOMNode* n, const XMLCh* ns, const XMLCh* local)
226 {
227     return (n && XMLString::equals(local,n->getLocalName()) && XMLString::equals(ns,n->getNamespaceURI()));
228 }
229
230 const XMLCh* XMLHelper::getTextContent(const DOMElement* e)
231 {
232     DOMNode* child = e ? e->getFirstChild() : nullptr;
233     while (child) {
234         if (child->getNodeType() == DOMNode::TEXT_NODE)
235             return child->getNodeValue();
236         child = child->getNextSibling();
237     }
238     return nullptr;
239 }
240
241 DOMElement* XMLHelper::getFirstChildElement(const DOMNode* n, const XMLCh* localName)
242 {
243     DOMNode* child = n ? n->getFirstChild() : nullptr;
244     while (child && child->getNodeType() != DOMNode::ELEMENT_NODE)
245         child = child->getNextSibling();
246     if (child && localName) {
247         if (!XMLString::equals(localName,child->getLocalName()))
248             return getNextSiblingElement(child, localName);
249     }
250     return static_cast<DOMElement*>(child);
251 }    
252
253 DOMElement* XMLHelper::getLastChildElement(const DOMNode* n, const XMLCh* localName)
254 {
255     DOMNode* child = n ? n->getLastChild() : nullptr;
256     while (child && child->getNodeType() != DOMNode::ELEMENT_NODE)
257         child = child->getPreviousSibling();
258     if (child && localName) {
259         if (!XMLString::equals(localName,child->getLocalName()))
260             return getPreviousSiblingElement(child, localName);
261     }
262     return static_cast<DOMElement*>(child);
263 }    
264
265 DOMElement* XMLHelper::getFirstChildElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName)
266 {
267     DOMElement* e = getFirstChildElement(n, localName);
268     while (e && !XMLString::equals(e->getNamespaceURI(),ns))
269         e = getNextSiblingElement(e, localName);
270     return e;
271 }
272
273 DOMElement* XMLHelper::getLastChildElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName)
274 {
275     DOMElement* e = getLastChildElement(n, localName);
276     while (e && !XMLString::equals(e->getNamespaceURI(),ns))
277         e = getPreviousSiblingElement(e, localName);
278     return e;
279 }
280
281 DOMElement* XMLHelper::getNextSiblingElement(const DOMNode* n, const XMLCh* localName)
282 {
283     DOMNode* sib = n ? n->getNextSibling() : nullptr;
284     while (sib && sib->getNodeType() != DOMNode::ELEMENT_NODE)
285         sib = sib->getNextSibling();
286     if (sib && localName) {
287         if (!XMLString::equals(localName,sib->getLocalName()))
288             return getNextSiblingElement(sib, localName);
289     }   
290     return static_cast<DOMElement*>(sib);
291 }
292
293 DOMElement* XMLHelper::getPreviousSiblingElement(const DOMNode* n, const XMLCh* localName)
294 {
295     DOMNode* sib = n ? n->getPreviousSibling() : nullptr;
296     while (sib && sib->getNodeType() != DOMNode::ELEMENT_NODE)
297         sib = sib->getPreviousSibling();
298     if (sib && localName) {
299         if (!XMLString::equals(localName,sib->getLocalName()))
300             return getPreviousSiblingElement(sib, localName);
301     }   
302     return static_cast<DOMElement*>(sib);
303 }
304
305 DOMElement* XMLHelper::getNextSiblingElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName)
306 {
307     DOMElement* e = getNextSiblingElement(n, localName);
308     while (e && !XMLString::equals(e->getNamespaceURI(),ns))
309         e = getNextSiblingElement(e, localName);
310     return e;
311 }
312
313 DOMElement* XMLHelper::getPreviousSiblingElement(const DOMNode* n, const XMLCh* ns, const XMLCh* localName)
314 {
315     DOMElement* e = getPreviousSiblingElement(n, localName);
316     while (e && !XMLString::equals(e->getNamespaceURI(),ns))
317         e = getPreviousSiblingElement(e, localName);
318     return e;
319 }
320
321 string XMLHelper::getAttrString(const DOMElement* e, const char* defValue, const XMLCh* localName, const XMLCh* ns)
322 {
323     if (e) {
324         auto_ptr_char val(e->getAttributeNS(ns, localName));
325         if (val.get() && *val.get())
326             return val.get();
327     }
328     return defValue ? defValue : "";
329 }
330
331 int XMLHelper::getAttrInt(const DOMElement* e, int defValue, const XMLCh* localName, const XMLCh* ns)
332 {
333     if (e) {
334         const XMLCh* val = e->getAttributeNS(ns, localName);
335         if (val && *val) {
336             int i = XMLString::parseInt(val);
337             if (i)
338                 return i;
339         }
340     }
341     return defValue;
342 }
343
344 bool XMLHelper::getAttrBool(const DOMElement* e, bool defValue, const XMLCh* localName, const XMLCh* ns)
345 {
346     if (e) {
347         const XMLCh* val = e->getAttributeNS(ns, localName);
348         if (val) {
349             if (*val == chLatin_t || *val == chDigit_1)
350                 return true;
351             if (*val == chLatin_f || *val == chDigit_0)
352                 return false;
353         }
354     }
355     return defValue;
356 }
357
358 void XMLHelper::serialize(const DOMNode* n, std::string& buf, bool pretty)
359 {
360     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };
361     static const XMLCh UTF8[]={ chLatin_U, chLatin_T, chLatin_F, chDash, chDigit_8, chNull };
362
363     MemBufFormatTarget target;
364     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);
365
366 #ifdef XMLTOOLING_XERCESC_COMPLIANT_DOMLS
367     DOMLSSerializer* serializer = static_cast<DOMImplementationLS*>(impl)->createLSSerializer();
368     XercesJanitor<DOMLSSerializer> janitor(serializer);
369     if (pretty && serializer->getDomConfig()->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, pretty))
370         serializer->getDomConfig()->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, pretty);
371     DOMLSOutput *theOutput = static_cast<DOMImplementationLS*>(impl)->createLSOutput();
372     XercesJanitor<DOMLSOutput> j_theOutput(theOutput);
373     theOutput->setEncoding(UTF8);
374     theOutput->setByteStream(&target);
375     if (!serializer->write(n, theOutput))
376         throw XMLParserException("unable to serialize XML");
377 #else
378     DOMWriter* serializer = static_cast<DOMImplementationLS*>(impl)->createDOMWriter();
379     XercesJanitor<DOMWriter> janitor(serializer);
380     serializer->setEncoding(UTF8);
381     if (pretty && serializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, pretty))
382         serializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, pretty);
383     if (!serializer->writeNode(&target, *n))
384         throw XMLParserException("unable to serialize XML");
385 #endif
386
387     buf.erase();
388     buf.append(reinterpret_cast<const char*>(target.getRawBuffer()),target.getLen());
389 }
390
391 namespace {
392     class StreamFormatTarget : public XMLFormatTarget
393     {
394     public:
395         StreamFormatTarget(std::ostream& out) : m_out(out) {}
396         ~StreamFormatTarget() {}
397
398         void writeChars(const XMLByte *const toWrite, const xsecsize_t count, XMLFormatter *const formatter) {
399             m_out.write(reinterpret_cast<const char*>(toWrite),count);
400         }
401
402         void flush() {
403             m_out.flush();
404         }
405
406     private:
407         std::ostream& m_out;
408     };
409 };
410
411 ostream& XMLHelper::serialize(const DOMNode* n, ostream& out, bool pretty)
412 {
413     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };
414     static const XMLCh UTF8[]={ chLatin_U, chLatin_T, chLatin_F, chDash, chDigit_8, chNull };
415
416     StreamFormatTarget target(out);
417     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);
418
419 #ifdef XMLTOOLING_XERCESC_COMPLIANT_DOMLS
420     DOMLSSerializer* serializer = static_cast<DOMImplementationLS*>(impl)->createLSSerializer();
421     XercesJanitor<DOMLSSerializer> janitor(serializer);
422     if (pretty && serializer->getDomConfig()->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, pretty))
423         serializer->getDomConfig()->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, pretty);
424     DOMLSOutput *theOutput = static_cast<DOMImplementationLS*>(impl)->createLSOutput();
425     XercesJanitor<DOMLSOutput> j_theOutput(theOutput);
426     theOutput->setEncoding(UTF8);
427     theOutput->setByteStream(&target);
428     if (!serializer->write(n, theOutput))
429         throw XMLParserException("unable to serialize XML");
430 #else
431     DOMWriter* serializer=(static_cast<DOMImplementationLS*>(impl))->createDOMWriter();
432     XercesJanitor<DOMWriter> janitor(serializer);
433     serializer->setEncoding(UTF8);
434     if (pretty && serializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, pretty))
435         serializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, pretty);
436     if (!serializer->writeNode(&target,*n))
437         throw XMLParserException("unable to serialize XML");
438 #endif
439
440     return out;
441 }
442
443 ostream& xmltooling::operator<<(ostream& ostr, const DOMNode& node)
444 {
445     return XMLHelper::serialize(&node, ostr);
446 }
447
448 ostream& xmltooling::operator<<(ostream& ostr, const XMLObject& obj)
449 {
450     return ostr << *(obj.marshall());
451 }