Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/unoxml/source/xpath/xpathapi.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include "xpathapi.hxx"
21
22
#include <stdarg.h>
23
#include <string.h>
24
25
#include <libxml/tree.h>
26
#include <libxml/xmlerror.h>
27
#include <libxml/xpath.h>
28
#include <libxml/xpathInternals.h>
29
#include <libxml/xmlIO.h>
30
31
#include <com/sun/star/xml/xpath/XPathException.hpp>
32
33
#include <rtl/ustrbuf.hxx>
34
#include <sal/log.hxx>
35
36
#include "xpathobject.hxx"
37
38
#include <node.hxx>
39
#include "../dom/document.hxx"
40
41
#include <comphelper/servicehelper.hxx>
42
#include <cppuhelper/supportsservice.hxx>
43
44
using namespace css::uno;
45
using namespace css::xml::dom;
46
using namespace css::xml::xpath;
47
48
namespace XPath
49
{
50
    // ctor
51
    CXPathAPI::CXPathAPI(const Reference< XComponentContext >& rxContext)
52
515
        : m_xContext(rxContext)
53
515
    {
54
515
    }
55
56
    Sequence< OUString > SAL_CALL CXPathAPI::getSupportedServiceNames()
57
0
    {
58
0
        return { u"com.sun.star.xml.xpath.XPathAPI"_ustr };
59
0
    }
60
61
    OUString SAL_CALL CXPathAPI::getImplementationName()
62
0
    {
63
0
        return u"com.sun.star.comp.xml.xpath.XPathAPI"_ustr;
64
0
    }
65
66
    sal_Bool SAL_CALL CXPathAPI::supportsService(const OUString& aServiceName)
67
0
    {
68
0
        return cppu::supportsService(this, aServiceName);
69
0
    }
70
71
    void SAL_CALL CXPathAPI::registerNS(
72
            const OUString& aPrefix,
73
            const OUString& aURI)
74
863
    {
75
863
        std::scoped_lock const g(m_Mutex);
76
77
863
        m_nsmap.emplace(aPrefix, aURI);
78
863
    }
79
80
    void SAL_CALL CXPathAPI::unregisterNS(
81
            const OUString& aPrefix,
82
            const OUString& aURI)
83
0
    {
84
0
        std::scoped_lock const g(m_Mutex);
85
86
0
        if ((m_nsmap.find(aPrefix))->second == aURI) {
87
0
            m_nsmap.erase(aPrefix);
88
0
        }
89
0
    }
90
91
    // register all namespaces stored in the namespace list for this object
92
    // with the current xpath evaluation context
93
    static void lcl_registerNamespaces(
94
            xmlXPathContextPtr ctx,
95
            const nsmap_t& nsmap)
96
721
    {
97
721
        OString oprefix, ouri;
98
721
        for (const auto& rEntry : nsmap)
99
1.20k
        {
100
1.20k
            oprefix = OUStringToOString(rEntry.first,  RTL_TEXTENCODING_UTF8);
101
1.20k
            ouri    = OUStringToOString(rEntry.second, RTL_TEXTENCODING_UTF8);
102
1.20k
            xmlChar const *p = reinterpret_cast<xmlChar const *>(oprefix.getStr());
103
1.20k
            xmlChar const *u = reinterpret_cast<xmlChar const *>(ouri.getStr());
104
1.20k
            (void)xmlXPathRegisterNs(ctx, p, u);
105
1.20k
        }
106
721
    }
107
108
    // get all ns decls on a node (and parent nodes, if any)
109
    static void lcl_collectNamespaces(
110
            nsmap_t & rNamespaces, Reference< XNode > const& xNamespaceNode)
111
0
    {
112
0
        DOM::CNode *const pCNode(dynamic_cast<DOM::CNode*>(xNamespaceNode.get()));
113
0
        if (!pCNode) { throw RuntimeException(u"Could not use the namespace node in order to collect namespace declarations."_ustr); }
114
115
0
        ::osl::MutexGuard const g(pCNode->GetOwnerDocument().GetMutex());
116
117
0
        xmlNodePtr pNode = pCNode->GetNodePtr();
118
0
        while (pNode != nullptr) {
119
0
            xmlNsPtr curDef = pNode->nsDef;
120
0
            while (curDef != nullptr) {
121
0
                const xmlChar* pHref = curDef->href;
122
0
                OUString aURI(reinterpret_cast<char const *>(pHref), strlen(reinterpret_cast<char const *>(pHref)), RTL_TEXTENCODING_UTF8);
123
0
                const xmlChar* pPre = curDef->prefix;
124
0
                OUString aPrefix(reinterpret_cast<char const *>(pPre), strlen(reinterpret_cast<char const *>(pPre)), RTL_TEXTENCODING_UTF8);
125
                // we could already have this prefix from a child node
126
0
                rNamespaces.emplace(aPrefix, aURI);
127
0
                curDef = curDef->next;
128
0
            }
129
0
            pNode = pNode->parent;
130
0
        }
131
0
    }
132
133
    static void lcl_collectRegisterNamespaces(
134
            CXPathAPI & rAPI, Reference< XNode > const& xNamespaceNode)
135
0
    {
136
0
        nsmap_t namespaces;
137
0
        lcl_collectNamespaces(namespaces, xNamespaceNode);
138
0
        for (const auto& rEntry : namespaces)
139
0
        {
140
0
            rAPI.registerNS(rEntry.first, rEntry.second);
141
0
        }
142
0
    }
143
144
    // register function and variable lookup functions with the current
145
    // xpath evaluation context
146
    static void lcl_registerExtensions(
147
            xmlXPathContextPtr ctx,
148
            const extensions_t& extensions)
149
721
    {
150
721
        for (const auto& rExtensionRef : extensions)
151
0
        {
152
0
            Libxml2ExtensionHandle aHandle = rExtensionRef->getLibxml2ExtensionHandle();
153
0
            if ( aHandle.functionLookupFunction != 0 )
154
0
            {
155
0
                xmlXPathRegisterFuncLookup(ctx,
156
0
                    reinterpret_cast<xmlXPathFuncLookupFunc>(
157
0
                        sal::static_int_cast<sal_IntPtr>(aHandle.functionLookupFunction)),
158
0
                    reinterpret_cast<void*>(
159
0
                        sal::static_int_cast<sal_IntPtr>(aHandle.functionData)));
160
0
            }
161
0
            if ( aHandle.variableLookupFunction != 0 )
162
0
            {
163
0
                xmlXPathRegisterVariableLookup(ctx,
164
0
                    reinterpret_cast<xmlXPathVariableLookupFunc>(
165
0
                        sal::static_int_cast<sal_IntPtr>(aHandle.variableLookupFunction)),
166
0
                    reinterpret_cast<void*>(
167
0
                        sal::static_int_cast<sal_IntPtr>(aHandle.variableData)));
168
0
            }
169
0
        }
170
721
    }
171
172
    /**
173
     * Use an XPath string to select a nodelist.
174
     */
175
    Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeList(
176
            const Reference< XNode >& contextNode,
177
            const OUString& expr)
178
0
    {
179
0
        Reference< XXPathObject > xobj = eval(contextNode, expr);
180
0
        return xobj->getNodeList();
181
0
    }
182
183
    /**
184
     * same as selectNodeList but registers all name space declarations found on namespaceNode
185
     */
186
    Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeListNS(
187
            const Reference< XNode >&  contextNode,
188
            const OUString& expr,
189
            const Reference< XNode >&  namespaceNode)
190
0
    {
191
0
        lcl_collectRegisterNamespaces(*this, namespaceNode);
192
0
        return selectNodeList(contextNode, expr);
193
0
    }
194
195
    /**
196
     * Same as selectNodeList but returns the first node (if any)
197
     */
198
    Reference< XNode > SAL_CALL CXPathAPI::selectSingleNode(
199
            const Reference< XNode >& contextNode,
200
            const OUString& expr)
201
0
    {
202
0
        Reference< XNodeList > aList = selectNodeList(contextNode, expr);
203
0
        Reference< XNode > aNode = aList->item(0);
204
0
        return aNode;
205
0
    }
206
207
    /**
208
     * Same as selectSingleNode but registers all namespaces declared on
209
     * namespaceNode
210
     */
211
    Reference< XNode > SAL_CALL CXPathAPI::selectSingleNodeNS(
212
            const Reference< XNode >& contextNode,
213
            const OUString& expr,
214
            const Reference< XNode >&  namespaceNode )
215
0
    {
216
0
        lcl_collectRegisterNamespaces(*this, namespaceNode);
217
0
        return selectSingleNode(contextNode, expr);
218
0
    }
219
220
    static OUString make_error_message(const xmlError* pError)
221
0
    {
222
0
        OUStringBuffer buf;
223
0
        if (pError) {
224
0
            if (pError->message) {
225
0
                buf.appendAscii(pError->message);
226
0
            }
227
0
            int line = pError->line;
228
0
            if (line) {
229
0
                buf.append("Line: " + OUString::number(static_cast<sal_Int32>(line)) + "\n");
230
0
            }
231
0
            int column = pError->int2;
232
0
            if (column) {
233
0
                buf.append("Column: " + OUString::number(static_cast<sal_Int32>(column)) + "\n");
234
0
            }
235
0
        } else {
236
0
            buf.append("no error argument!");
237
0
        }
238
0
        OUString msg = buf.makeStringAndClear();
239
0
        return msg;
240
0
    }
241
242
    extern "C" {
243
244
#if defined __GNUC__
245
        __attribute__ ((format (printf, 2, 3)))
246
#endif
247
        static void generic_error_func(void *, const char *format, ...)
248
0
        {
249
0
            char str[1000];
250
0
            va_list args;
251
252
0
            va_start(args, format);
253
#ifdef _WIN32
254
#define vsnprintf _vsnprintf
255
#endif
256
0
            vsnprintf(str, sizeof(str), format, args);
257
0
            va_end(args);
258
259
0
            SAL_WARN("unoxml", "libxml2 error: " << str);
260
0
        }
261
262
#if LIBXML_VERSION >= 21200
263
        static void structured_error_func(void *, const xmlError* error)
264
#else
265
        static void structured_error_func(void *, xmlErrorPtr error)
266
#endif
267
0
        {
268
0
            SAL_WARN("unoxml", "libxml2 error: " << make_error_message(error));
269
0
        }
270
271
    } // extern "C"
272
273
    /**
274
     * evaluates an XPath string. relative XPath expressions are evaluated relative to
275
     * the context Node
276
     */
277
    Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
278
            Reference< XNode > const& xContextNode,
279
            const OUString& expr)
280
721
    {
281
721
        if (!xContextNode.is()) { throw RuntimeException(u"xContextNode does not exist!"_ustr); }
282
283
721
        nsmap_t nsmap;
284
721
        extensions_t extensions;
285
286
721
        {
287
721
            std::scoped_lock const g(m_Mutex);
288
721
            nsmap = m_nsmap;
289
721
            extensions = m_extensions;
290
721
        }
291
292
        // get the node and document
293
721
        ::rtl::Reference<DOM::CDocument> const pCDoc(
294
721
                dynamic_cast<DOM::CDocument*>(xContextNode->getOwnerDocument().get()));
295
721
        if (!pCDoc.is()) { throw RuntimeException(u"Interface pointer for the owner document of the xContextNode does not exist."_ustr); }
296
297
721
        DOM::CNode *const pCNode = dynamic_cast<DOM::CNode*>(xContextNode.get());
298
721
        if (!pCNode) { throw RuntimeException(u"xContextNode interface pointer does not exist."_ustr); }
299
300
721
        ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document!
301
302
721
        xmlNodePtr const pNode = pCNode->GetNodePtr();
303
721
        if (!pNode) { throw RuntimeException(u"Node pointer for xContextNode does not exist."_ustr); }
304
721
        xmlDocPtr pDoc = pNode->doc;
305
306
        /* NB: workaround for #i87252#:
307
           libxml < 2.6.17 considers it an error if the context
308
           node is the empty document (i.e. its xpathCtx->doc has no
309
           children). libxml 2.6.17 does not consider it an error.
310
           Unfortunately, old libxml prints an error message to stderr,
311
           which (afaik) cannot be turned off in this case, so we handle it.
312
        */
313
721
        if (!pDoc->children) {
314
0
            throw XPathException();
315
0
        }
316
317
        /* Create xpath evaluation context */
318
721
        std::shared_ptr<xmlXPathContext> const xpathCtx(
319
721
                xmlXPathNewContext(pDoc), xmlXPathFreeContext);
320
721
        if (xpathCtx == nullptr) { throw XPathException(); }
321
322
        // set context node
323
721
        xpathCtx->node = pNode;
324
        // error handling
325
721
        xpathCtx->error = structured_error_func;
326
721
        xmlSetGenericErrorFunc(nullptr, generic_error_func);
327
328
        // register namespaces and extension
329
721
        lcl_registerNamespaces(xpathCtx.get(), nsmap);
330
721
        lcl_registerExtensions(xpathCtx.get(), extensions);
331
332
        /* run the query */
333
721
        OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8);
334
721
        xmlChar const *pStr = reinterpret_cast<xmlChar const *>(o1.getStr());
335
721
        std::shared_ptr<xmlXPathObject> const xpathObj(
336
721
                xmlXPathEval(pStr, xpathCtx.get()), xmlXPathFreeObject);
337
721
        xmlSetGenericErrorFunc(nullptr, nullptr);
338
721
        if (nullptr == xpathObj) {
339
            // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
340
0
            throw XPathException();
341
0
        }
342
721
        Reference<XXPathObject> const xObj(
343
721
                new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj));
344
721
        return xObj;
345
721
    }
346
347
    /**
348
     * same as eval but registers all namespace declarations found on namespaceNode
349
     */
350
    Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS(
351
            const Reference< XNode >& contextNode,
352
            const OUString& expr,
353
            const Reference< XNode >& namespaceNode)
354
0
    {
355
0
        lcl_collectRegisterNamespaces(*this, namespaceNode);
356
0
        return eval(contextNode, expr);
357
0
    }
358
359
    /**
360
     * uses the service manager to create an instance of the service denoted by aName.
361
     * If the returned object implements the XXPathExtension interface, it is added to the list
362
     * of extensions that are used when evaluating XPath strings with this XPathAPI instance
363
     */
364
    void SAL_CALL CXPathAPI::registerExtension(
365
            const OUString& aName)
366
0
    {
367
0
        std::scoped_lock const g(m_Mutex);
368
369
        // get extension from service manager
370
0
        Reference< XXPathExtension > const xExtension(
371
0
                m_xContext->getServiceManager()->createInstanceWithContext(aName, m_xContext), UNO_QUERY_THROW);
372
0
        m_extensions.push_back(xExtension);
373
0
    }
374
375
    /**
376
     * registers the given extension instance to be used by XPath evaluations performed through this
377
     * XPathAPI instance
378
     */
379
    void SAL_CALL CXPathAPI::registerExtensionInstance(
380
            Reference< XXPathExtension> const& xExtension)
381
0
    {
382
0
        if (!xExtension.is()) {
383
0
            throw RuntimeException(u"Extension instance xExtension to be used by XPath does not exist."_ustr);
384
0
        }
385
0
        std::scoped_lock const g(m_Mutex);
386
0
        m_extensions.push_back( xExtension );
387
0
    }
388
}
389
390
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
391
unoxml_CXPathAPI_get_implementation(
392
    css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
393
515
{
394
515
    return cppu::acquire(new XPath::CXPathAPI(context));
395
515
}
396
397
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */