Update date in Doxyfile
[anna.git] / source / xml / XPath.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8
9 #include <ctype.h>
10
11 #include <libxml/xpath.h>
12 #include <libxml/xpathInternals.h>
13 #include <libxml/parser.h>
14
15 #include <anna/core/functions.hpp>
16 #include <anna/core/tracing/Logger.hpp>
17 #include <anna/core/tracing/TraceMethod.hpp>
18 #include <anna/core/tracing/TraceFunction.hpp>
19
20 #include <anna/xml/XPath.hpp>
21 #include <anna/xml/internal/sccs.hpp>
22 #include <anna/xml/Document.hpp>
23
24 using namespace std;
25 using namespace anna;
26
27 xml::XPath::XPath(const char* name) {
28   a_root = new Node(name);
29   sccs::activate();
30 }
31
32 /*
33  * Según la implementación de
34  * http://www.xmlsoft.org/examples/xpath1.c
35  *
36  * Los nodos se recuperan mediante los métodos node_begin, node_end y node
37  */
38 void xml::XPath::apply(const Document& document, const char* expression, const int mode)
39 throw(RuntimeException) {
40   a_mode = mode;
41   ConfigSkeleton configSkeleton(&XPath::callbackApply);
42
43   if((mode & Mode::Namespace) != 0)
44     configSkeleton.initialize = &XPath::initializeNamespaces;
45
46   skeleton(document, expression,  configSkeleton);
47 }
48
49 bool xml::XPath::match(const Document& document, const char* expression, const int mode)
50 throw(RuntimeException) {
51   a_result = false;
52   ConfigSkeleton configSkeleton(&XPath::callbackMatch);
53
54   if((mode & Mode::Namespace) != 0)
55     configSkeleton.initialize = &XPath::initializeNamespaces;
56
57   skeleton(document, expression, configSkeleton);
58   LOGDEBUG(
59     string msg("xml::XPath::match | Expression: ");
60     msg += expression;
61     msg += anna::functions::asText(" | Result: ", a_result);
62     Logger::debug(msg, ANNA_FILE_LOCATION);
63   );
64   return a_result;
65 }
66
67 const xml::Node* xml::XPath::find(const Document& document, const char* expression, const int mode, const Exception::Mode::_v emode)
68 throw(RuntimeException) {
69   apply(document, expression, mode);
70   Node::const_child_iterator ii = node_begin();
71
72   if(ii == node_end()) {
73     if(emode == Exception::Mode::Throw) {
74       string msg("xml::XPath::find | Expression: ");
75       msg += expression;
76       msg += " | Does not match any xml::Node";
77       throw RuntimeException(msg, ANNA_FILE_LOCATION);
78     } else if(emode == Exception::Mode::Trace) {
79       string msg("xml::XPath::find | Expression: ");
80       msg += expression;
81       msg += " | Does not match any xml::Node";
82       Logger::warning(msg, ANNA_FILE_LOCATION);
83     }
84
85     return NULL;
86   }
87
88   if(size() > 1) {
89     string msg("xml::XPath::find | Expression: ");
90     msg += expression;
91     msg += functions::asString(" | Ignores %d xml::Node's", size());
92     Logger::warning(msg, ANNA_FILE_LOCATION);
93   }
94
95   return XPath::node(ii);
96 }
97
98 void xml::XPath::skeleton(const Document& document, const char* expression, xml::XPath::ConfigSkeleton& config)
99 throw(RuntimeException) {
100   LOGDEBUG(
101     string msg("xml::XPath::skeleton | Document: ");
102     msg += functions::asString(document.getContent());
103     msg += " | Expression: ";
104     msg += expression;
105     Logger::debug(msg, ANNA_FILE_LOCATION);
106   );
107
108   if(document.a_handle == NULL)
109     throw RuntimeException("xml::XPath::pattern | xml::Document had not been initialized", ANNA_FILE_LOCATION);
110
111   setupEncoding(document.a_handle);
112   a_root->clear();
113   xmlXPathContextPtr context = NULL;
114   xmlXPathObjectPtr object = NULL;
115
116   try {
117     if((context = xmlXPathNewContext(document.a_handle)) == NULL)
118       throw RuntimeException("Can not create XPath context", ANNA_FILE_LOCATION);
119
120     if(config.initialize != NULL)
121       (this->*config.initialize)(context, document);
122
123     if((object = xmlXPathEvalExpression(BAD_CAST expression, context)) == NULL) {
124       string msg("xml::XPath::skeleton | Expression: ");
125       msg += expression;
126       msg += " | Unable to evaluate expression";
127       throw RuntimeException(msg, ANNA_FILE_LOCATION);
128     }
129
130     (this->*config.callback)(object->nodesetval);
131     xmlXPathFreeObject(object);
132     xmlXPathFreeContext(context);
133   } catch(RuntimeException& ex) {
134     if(object != NULL)
135       xmlXPathFreeObject(object);
136
137     if(context != NULL)
138       xmlXPathFreeContext(context);
139
140     throw;
141   }
142 }
143
144 /*
145  * Ha que tener en cuenta que puede haber definiciones de NS en cualquier nodo
146  * lo que nos obliga a recorrerlo en profundidad.
147  */
148 void xml::XPath::initializeNamespaces(_xmlXPathContext* context, const Document& document)
149 throw(RuntimeException) {
150   xmlNode* root;
151
152   if((root = xmlDocGetRootElement(document.a_handle)) == NULL)
153     throw RuntimeException("Error interpreting the XML document", ANNA_FILE_LOCATION);
154
155   // Incorpora la definición de los namespaces usados en el documento y los define en el contexto XPath
156   forwardNamespaces(context, root);
157 }
158
159 void xml::XPath::forwardNamespaces(_xmlXPathContext* context, _xmlNode* xmlNode)
160 throw(RuntimeException) {
161   while(xmlNode != NULL) {
162     if(xmlNode->type == XML_ELEMENT_NODE) {
163       for(xmlNs* ns = xmlNode->nsDef; ns != NULL; ns = ns->next) {
164         a_root->createNamespace((const char*) ns->prefix, (const char*) ns->href);
165         xmlXPathRegisterNs(context, ns->prefix, ns->href);
166         LOGDEBUG(
167           string msg("xml::XPath::forwardNamespaces | Prefix: ");
168           msg += (const char*) ns->prefix;
169           msg += " | HRef: ";
170           msg += (const char*) ns->href;
171           Logger::debug(msg, ANNA_FILE_LOCATION);
172         );
173       }
174
175       forwardNamespaces(context, xmlNode->children);
176     }
177
178     xmlNode = xmlNode->next;
179   }
180 }
181
182 void xml::XPath::callbackApply(const _xmlNodeSet* nodes) {
183   /* Si no hay nodos que cumplan con la expresión */
184   if(nodes == NULL)
185     return;
186
187   Node* node;
188   xmlNodePtr xmlNode;
189
190   for(int ii = 0, maxii = nodes->nodeNr; ii < maxii; ii ++) {
191     xmlNode = nodes->nodeTab [ii];
192
193     if(xmlNode->type != XML_ELEMENT_NODE)
194       continue;
195
196     /* Sólo analiza el primer nivel de dependencia de cada uno de los nodos seleccionados */
197     node = a_root->createChild((const char*) xmlNode->name);
198     attributes(node, xmlNode->properties);
199
200     if(xmlNode->ns != NULL)
201       node->setNamespace(a_root->namespace_find((const char*) xmlNode->ns->prefix));
202
203     if((a_mode & Mode::Full) != 0)
204       children(node, xmlNode->children);
205     else
206       text(node, xmlNode->children);
207
208     LOGDEBUG(Logger::debug(node->asString(), ANNA_FILE_LOCATION));
209   }
210 }
211
212 void xml::XPath::callbackMatch(const _xmlNodeSet* nodes) {
213   a_result = (nodes == NULL) ? false : (nodes->nodeNr > 0);
214 }
215
216 void xml::XPath::text(Node* node, xmlNode* xmlNode)
217 throw(RuntimeException) {
218   bool isSeparator;
219   const char* w;
220
221   while(xmlNode != NULL) {
222     switch(xmlNode->type) {
223     case XML_TEXT_NODE:
224       w = (const char*) xmlNode->content;
225       isSeparator = true;
226
227       while(*w != 0) {
228         if(isspace(*w) == false) {
229           isSeparator = false;
230           break;
231         }
232
233         w ++;
234       }
235
236       if(isSeparator == false)
237         node->createText(decode(xmlNode->content));
238
239       return;
240
241       default:
242        ;
243     }
244
245     xmlNode = xmlNode->next;
246   }
247 }
248
249
250