77803d9d94423585cc90938b8f12f14a36b2f17b
[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   const xml::Node* result = NULL;
71   Node::const_child_iterator ii = node_begin();
72
73   if(ii == node_end()) {
74     if(emode == Exception::Mode::Throw) {
75       string msg("xml::XPath::find | Expression: ");
76       msg += expression;
77       msg += " | Does not match any xml::Node";
78       throw RuntimeException(msg, ANNA_FILE_LOCATION);
79     } else if(emode == Exception::Mode::Trace) {
80       string msg("xml::XPath::find | Expression: ");
81       msg += expression;
82       msg += " | Does not match any xml::Node";
83       Logger::warning(msg, ANNA_FILE_LOCATION);
84     }
85
86     return NULL;
87   }
88
89   if(size() > 1) {
90     string msg("xml::XPath::find | Expression: ");
91     msg += expression;
92     msg += functions::asString(" | Ignores %d xml::Node's", size());
93     Logger::warning(msg, ANNA_FILE_LOCATION);
94   }
95
96   return XPath::node(ii);
97 }
98
99 void xml::XPath::skeleton(const Document& document, const char* expression, xml::XPath::ConfigSkeleton& config)
100 throw(RuntimeException) {
101   LOGDEBUG(
102     string msg("xml::XPath::skeleton | Document: ");
103     msg += functions::asString(document.getContent());
104     msg += " | Expression: ";
105     msg += expression;
106     Logger::debug(msg, ANNA_FILE_LOCATION);
107   );
108
109   if(document.a_handle == NULL)
110     throw RuntimeException("xml::XPath::pattern | xml::Document had not been initialized", ANNA_FILE_LOCATION);
111
112   setupEncoding(document.a_handle);
113   a_root->clear();
114   xmlXPathContextPtr context = NULL;
115   xmlXPathObjectPtr object = NULL;
116
117   try {
118     if((context = xmlXPathNewContext(document.a_handle)) == NULL)
119       throw RuntimeException("Can not create XPath context", ANNA_FILE_LOCATION);
120
121     if(config.initialize != NULL)
122       (this->*config.initialize)(context, document);
123
124     if((object = xmlXPathEvalExpression(BAD_CAST expression, context)) == NULL) {
125       string msg("xml::XPath::skeleton | Expression: ");
126       msg += expression;
127       msg += " | Unable to evaluate expression";
128       throw RuntimeException(msg, ANNA_FILE_LOCATION);
129     }
130
131     (this->*config.callback)(object->nodesetval);
132     xmlXPathFreeObject(object);
133     xmlXPathFreeContext(context);
134   } catch(RuntimeException& ex) {
135     if(object != NULL)
136       xmlXPathFreeObject(object);
137
138     if(context != NULL)
139       xmlXPathFreeContext(context);
140
141     throw;
142   }
143 }
144
145 /*
146  * Ha que tener en cuenta que puede haber definiciones de NS en cualquier nodo
147  * lo que nos obliga a recorrerlo en profundidad.
148  */
149 void xml::XPath::initializeNamespaces(_xmlXPathContext* context, const Document& document)
150 throw(RuntimeException) {
151   xmlNode* root;
152
153   if((root = xmlDocGetRootElement(document.a_handle)) == NULL)
154     throw RuntimeException("Error interpreting the XML document", ANNA_FILE_LOCATION);
155
156   // Incorpora la definición de los namespaces usados en el documento y los define en el contexto XPath
157   forwardNamespaces(context, root);
158 }
159
160 void xml::XPath::forwardNamespaces(_xmlXPathContext* context, _xmlNode* xmlNode)
161 throw(RuntimeException) {
162   while(xmlNode != NULL) {
163     if(xmlNode->type == XML_ELEMENT_NODE) {
164       for(xmlNs* ns = xmlNode->nsDef; ns != NULL; ns = ns->next) {
165         a_root->createNamespace((const char*) ns->prefix, (const char*) ns->href);
166         xmlXPathRegisterNs(context, ns->prefix, ns->href);
167         LOGDEBUG(
168           string msg("xml::XPath::forwardNamespaces | Prefix: ");
169           msg += (const char*) ns->prefix;
170           msg += " | HRef: ";
171           msg += (const char*) ns->href;
172           Logger::debug(msg, ANNA_FILE_LOCATION);
173         );
174       }
175
176       forwardNamespaces(context, xmlNode->children);
177     }
178
179     xmlNode = xmlNode->next;
180   }
181 }
182
183 void xml::XPath::callbackApply(const _xmlNodeSet* nodes) {
184   /* Si no hay nodos que cumplan con la expresión */
185   if(nodes == NULL)
186     return;
187
188   Node* node;
189   xmlNodePtr xmlNode;
190
191   for(int ii = 0, maxii = nodes->nodeNr; ii < maxii; ii ++) {
192     xmlNode = nodes->nodeTab [ii];
193
194     if(xmlNode->type != XML_ELEMENT_NODE)
195       continue;
196
197     /* Sólo analiza el primer nivel de dependencia de cada uno de los nodos seleccionados */
198     node = a_root->createChild((const char*) xmlNode->name);
199     attributes(node, xmlNode->properties);
200
201     if(xmlNode->ns != NULL)
202       node->setNamespace(a_root->namespace_find((const char*) xmlNode->ns->prefix));
203
204     if((a_mode & Mode::Full) != 0)
205       children(node, xmlNode->children);
206     else
207       text(node, xmlNode->children);
208
209     LOGDEBUG(Logger::debug(node->asString(), ANNA_FILE_LOCATION));
210   }
211 }
212
213 void xml::XPath::callbackMatch(const _xmlNodeSet* nodes) {
214   a_result = (nodes == NULL) ? false : (nodes->nodeNr > 0);
215 }
216
217 void xml::XPath::text(Node* node, xmlNode* xmlNode)
218 throw(RuntimeException) {
219   bool isSeparator;
220   const char* w;
221
222   while(xmlNode != NULL) {
223     switch(xmlNode->type) {
224     case XML_TEXT_NODE:
225       w = (const char*) xmlNode->content;
226       isSeparator = true;
227
228       while(*w != 0) {
229         if(isspace(*w) == false) {
230           isSeparator = false;
231           break;
232         }
233
234         w ++;
235       }
236
237       if(isSeparator == false)
238         node->createText(decode(xmlNode->content));
239
240       return;
241     }
242
243     xmlNode = xmlNode->next;
244   }
245 }
246
247
248