First commit
[anna.git] / source / xml / XPath.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <ctype.h>
38
39 #include <libxml/xpath.h>
40 #include <libxml/xpathInternals.h>
41 #include <libxml/parser.h>
42
43 #include <anna/core/functions.hpp>
44 #include <anna/core/tracing/Logger.hpp>
45 #include <anna/core/tracing/TraceMethod.hpp>
46 #include <anna/core/tracing/TraceFunction.hpp>
47
48 #include <anna/xml/XPath.hpp>
49 #include <anna/xml/internal/sccs.hpp>
50 #include <anna/xml/Document.hpp>
51
52 using namespace std;
53 using namespace anna;
54
55 xml::XPath::XPath(const char* name) {
56   a_root = new Node(name);
57   sccs::activate();
58 }
59
60 /*
61  * Según la implementación de
62  * http://www.xmlsoft.org/examples/xpath1.c
63  *
64  * Los nodos se recuperan mediante los métodos node_begin, node_end y node
65  */
66 void xml::XPath::apply(const Document& document, const char* expression, const int mode)
67 throw(RuntimeException) {
68   a_mode = mode;
69   ConfigSkeleton configSkeleton(&XPath::callbackApply);
70
71   if((mode & Mode::Namespace) != 0)
72     configSkeleton.initialize = &XPath::initializeNamespaces;
73
74   skeleton(document, expression,  configSkeleton);
75 }
76
77 bool xml::XPath::match(const Document& document, const char* expression, const int mode)
78 throw(RuntimeException) {
79   a_result = false;
80   ConfigSkeleton configSkeleton(&XPath::callbackMatch);
81
82   if((mode & Mode::Namespace) != 0)
83     configSkeleton.initialize = &XPath::initializeNamespaces;
84
85   skeleton(document, expression, configSkeleton);
86   LOGDEBUG(
87     string msg("xml::XPath::match | Expression: ");
88     msg += expression;
89     msg += anna::functions::asText(" | Result: ", a_result);
90     Logger::debug(msg, ANNA_FILE_LOCATION);
91   );
92   return a_result;
93 }
94
95 const xml::Node* xml::XPath::find(const Document& document, const char* expression, const int mode, const Exception::Mode::_v emode)
96 throw(RuntimeException) {
97   apply(document, expression, mode);
98   const xml::Node* result = NULL;
99   Node::const_child_iterator ii = node_begin();
100
101   if(ii == node_end()) {
102     if(emode == Exception::Mode::Throw) {
103       string msg("xml::XPath::find | Expression: ");
104       msg += expression;
105       msg += " | Does not match any xml::Node";
106       throw RuntimeException(msg, ANNA_FILE_LOCATION);
107     } else if(emode == Exception::Mode::Trace) {
108       string msg("xml::XPath::find | Expression: ");
109       msg += expression;
110       msg += " | Does not match any xml::Node";
111       Logger::warning(msg, ANNA_FILE_LOCATION);
112     }
113
114     return NULL;
115   }
116
117   if(size() > 1) {
118     string msg("xml::XPath::find | Expression: ");
119     msg += expression;
120     msg += functions::asString(" | Ignores %d xml::Node's", size());
121     Logger::warning(msg, ANNA_FILE_LOCATION);
122   }
123
124   return XPath::node(ii);
125 }
126
127 void xml::XPath::skeleton(const Document& document, const char* expression, xml::XPath::ConfigSkeleton& config)
128 throw(RuntimeException) {
129   LOGDEBUG(
130     string msg("xml::XPath::skeleton | Document: ");
131     msg += functions::asString(document.getContent());
132     msg += " | Expression: ";
133     msg += expression;
134     Logger::debug(msg, ANNA_FILE_LOCATION);
135   );
136
137   if(document.a_handle == NULL)
138     throw RuntimeException("xml::XPath::pattern | xml::Document had not been initialized", ANNA_FILE_LOCATION);
139
140   setupEncoding(document.a_handle);
141   a_root->clear();
142   xmlXPathContextPtr context = NULL;
143   xmlXPathObjectPtr object = NULL;
144
145   try {
146     if((context = xmlXPathNewContext(document.a_handle)) == NULL)
147       throw RuntimeException("Can not create XPath context", ANNA_FILE_LOCATION);
148
149     if(config.initialize != NULL)
150       (this->*config.initialize)(context, document);
151
152     if((object = xmlXPathEvalExpression(BAD_CAST expression, context)) == NULL) {
153       string msg("xml::XPath::skeleton | Expression: ");
154       msg += expression;
155       msg += " | Unable to evaluate expression";
156       throw RuntimeException(msg, ANNA_FILE_LOCATION);
157     }
158
159     (this->*config.callback)(object->nodesetval);
160     xmlXPathFreeObject(object);
161     xmlXPathFreeContext(context);
162   } catch(RuntimeException& ex) {
163     if(object != NULL)
164       xmlXPathFreeObject(object);
165
166     if(context != NULL)
167       xmlXPathFreeContext(context);
168
169     throw;
170   }
171 }
172
173 /*
174  * Ha que tener en cuenta que puede haber definiciones de NS en cualquier nodo
175  * lo que nos obliga a recorrerlo en profundidad.
176  */
177 void xml::XPath::initializeNamespaces(_xmlXPathContext* context, const Document& document)
178 throw(RuntimeException) {
179   xmlNode* root;
180
181   if((root = xmlDocGetRootElement(document.a_handle)) == NULL)
182     throw RuntimeException("Error interpreting the XML document", ANNA_FILE_LOCATION);
183
184   // Incorpora la definición de los namespaces usados en el documento y los define en el contexto XPath
185   forwardNamespaces(context, root);
186 }
187
188 void xml::XPath::forwardNamespaces(_xmlXPathContext* context, _xmlNode* xmlNode)
189 throw(RuntimeException) {
190   while(xmlNode != NULL) {
191     if(xmlNode->type == XML_ELEMENT_NODE) {
192       for(xmlNs* ns = xmlNode->nsDef; ns != NULL; ns = ns->next) {
193         a_root->createNamespace((const char*) ns->prefix, (const char*) ns->href);
194         xmlXPathRegisterNs(context, ns->prefix, ns->href);
195         LOGDEBUG(
196           string msg("xml::XPath::forwardNamespaces | Prefix: ");
197           msg += (const char*) ns->prefix;
198           msg += " | HRef: ";
199           msg += (const char*) ns->href;
200           Logger::debug(msg, ANNA_FILE_LOCATION);
201         );
202       }
203
204       forwardNamespaces(context, xmlNode->children);
205     }
206
207     xmlNode = xmlNode->next;
208   }
209 }
210
211 void xml::XPath::callbackApply(const _xmlNodeSet* nodes) {
212   /* Si no hay nodos que cumplan con la expresión */
213   if(nodes == NULL)
214     return;
215
216   Node* node;
217   xmlNodePtr xmlNode;
218
219   for(int ii = 0, maxii = nodes->nodeNr; ii < maxii; ii ++) {
220     xmlNode = nodes->nodeTab [ii];
221
222     if(xmlNode->type != XML_ELEMENT_NODE)
223       continue;
224
225     /* Sólo analiza el primer nivel de dependencia de cada uno de los nodos seleccionados */
226     node = a_root->createChild((const char*) xmlNode->name);
227     attributes(node, xmlNode->properties);
228
229     if(xmlNode->ns != NULL)
230       node->setNamespace(a_root->namespace_find((const char*) xmlNode->ns->prefix));
231
232     if((a_mode & Mode::Full) != 0)
233       children(node, xmlNode->children);
234     else
235       text(node, xmlNode->children);
236
237     LOGDEBUG(Logger::debug(node->asString(), ANNA_FILE_LOCATION));
238   }
239 }
240
241 void xml::XPath::callbackMatch(const _xmlNodeSet* nodes) {
242   a_result = (nodes == NULL) ? false : (nodes->nodeNr > 0);
243 }
244
245 void xml::XPath::text(Node* node, xmlNode* xmlNode)
246 throw(RuntimeException) {
247   bool isSeparator;
248   const char* w;
249
250   while(xmlNode != NULL) {
251     switch(xmlNode->type) {
252     case XML_TEXT_NODE:
253       w = (const char*) xmlNode->content;
254       isSeparator = true;
255
256       while(*w != 0) {
257         if(isspace(*w) == false) {
258           isSeparator = false;
259           break;
260         }
261
262         w ++;
263       }
264
265       if(isSeparator == false)
266         node->createText(decode(xmlNode->content));
267
268       return;
269     }
270
271     xmlNode = xmlNode->next;
272   }
273 }
274
275
276