1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
23 #include <comphelper/servicehelper.hxx>
24 #include <o3tl/string_view.hxx>
25 #include <sal/types.h>
26 #include <rtl/ustring.hxx>
27 #include <rtl/string.hxx>
28 #include <rtl/strbuf.hxx>
29 #include <tools/date.hxx>
30 #include <tools/time.hxx>
31 #include <tools/datetime.hxx>
33 #include <com/sun/star/uno/Reference.hxx>
34 #include <com/sun/star/uno/Sequence.hxx>
35 #include <com/sun/star/xforms/XModel.hpp>
36 #include <com/sun/star/xml/dom/XNode.hpp>
37 #include <com/sun/star/xml/dom/XDocument.hpp>
38 #include <com/sun/star/lang/XUnoTunnel.hpp>
40 #include <boost/lexical_cast.hpp>
41 #include <libxml/xpathInternals.h>
43 #include "xpathlib.hxx"
44 #include "extension.hxx"
48 using namespace com::sun::star::uno
;
49 using namespace com::sun::star::xml::dom
;
50 using namespace com::sun::star::xforms
;
51 using namespace com::sun::star::lang
;
53 xmlXPathFunction
xforms_lookupFunc(void *, const xmlChar
*xname
, const xmlChar
*)
56 const char *name
= reinterpret_cast<char const *>(xname
);
57 if (strcmp("boolean-from-string", name
)==0)
58 return xforms_booleanFromStringFunction
;
59 else if ((strcmp("if", name
))==0)
60 return xforms_ifFunction
;
61 else if ((strcmp("avg", name
))==0)
62 return xforms_avgFunction
;
63 else if ((strcmp("min", name
))==0)
64 return xforms_minFunction
;
65 else if ((strcmp("max", name
))==0)
66 return xforms_maxFunction
;
67 else if ((strcmp("count-non-empty", name
))==0)
68 return xforms_countNonEmptyFunction
;
69 else if ((strcmp("index", name
))==0)
70 return xforms_indexFunction
;
71 else if ((strcmp("property", name
))==0)
72 return xforms_propertyFunction
;
73 else if ((strcmp("now", name
))==0)
74 return xforms_nowFunction
;
75 else if ((strcmp("days-from-date", name
))==0)
76 return xforms_daysFromDateFunction
;
77 else if ((strcmp("seconds-from-dateTime", name
))==0)
78 return xforms_secondsFromDateTimeFunction
;
79 else if ((strcmp("seconds", name
))==0)
80 return xforms_secondsFunction
;
81 else if ((strcmp("months", name
))==0)
82 return xforms_monthsFunction
;
83 else if ((strcmp("instance", name
))==0)
84 return xforms_instanceFunction
;
85 else if ((strcmp("current", name
))==0)
86 return xforms_currentFunction
;
92 void xforms_booleanFromStringFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
94 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
95 xmlChar
*pString
= xmlXPathPopString(ctxt
);
96 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
97 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
98 if (aString
.equalsIgnoreAsciiCase("true") ||
99 aString
.equalsIgnoreAsciiCase("1"))
100 xmlXPathReturnTrue(ctxt
);
101 else if (aString
.equalsIgnoreAsciiCase("false") ||
102 aString
.equalsIgnoreAsciiCase("0"))
103 xmlXPathReturnFalse(ctxt
);
105 XP_ERROR(XPATH_NUMBER_ERROR
);
108 void xforms_ifFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
110 if (nargs
!= 3) XP_ERROR(XPATH_INVALID_ARITY
);
111 xmlChar
*s2
= xmlXPathPopString(ctxt
);
113 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
114 xmlChar
*s1
= xmlXPathPopString(ctxt
);
115 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
116 bool aBool
= xmlXPathPopBoolean(ctxt
);
117 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
120 xmlXPathReturnString(ctxt
, s1
);
122 xmlXPathReturnString(ctxt
, s2
);
127 void xforms_avgFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
129 // use sum(), div() and count()
130 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
133 xmlXPathObjectPtr pObject
= valuePop(ctxt
);
134 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
136 valuePush(ctxt
, xmlXPathObjectCopy(pObject
));
138 xmlXPathSumFunction(ctxt
, 1);
139 double nSum
= xmlXPathPopNumber(ctxt
);
140 // push a copy once more
141 valuePush(ctxt
, xmlXPathObjectCopy(pObject
));
142 xmlXPathCountFunction(ctxt
, 1);
143 double nCount
= xmlXPathPopNumber(ctxt
);
144 // push args for div()
145 xmlXPathReturnNumber(ctxt
, nSum
);
146 xmlXPathReturnNumber(ctxt
, nCount
);
147 xmlXPathDivValues(ctxt
);
148 // the result is now on the ctxt stack
149 xmlXPathFreeObject(pObject
);
152 void xforms_minFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
154 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
155 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
156 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
158 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
160 double nNumber
= xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet
, i
));
161 if (xmlXPathIsNaN(nNumber
))
163 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
168 else if (nNumber
< nMinimum
)
171 xmlXPathReturnNumber(ctxt
, nMinimum
);
174 void xforms_maxFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
176 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
177 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
178 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
180 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
182 double nNumber
= xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet
, i
));
183 if (xmlXPathIsNaN(nNumber
))
185 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
190 else if (nNumber
> nMaximum
)
193 xmlXPathReturnNumber(ctxt
, nMaximum
);
195 void xforms_countNonEmptyFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
197 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
198 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
199 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
200 sal_Int32 nNotEmpty
= 0;
201 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
203 const xmlChar
*aString
= xmlXPathCastNodeToString(xmlXPathNodeSetItem(pNodeSet
, i
));
204 if (*aString
!= 0) nNotEmpty
++;
206 xmlXPathReturnNumber(ctxt
, nNotEmpty
);
208 void xforms_indexFunction(xmlXPathParserContextPtr
/*ctxt*/, int /*nargs*/)
210 // function index takes a string argument that is the IDREF of a
211 // 'repeat' and returns the current 1-based position of the repeat
212 // index of the identified repeat -- see xforms/9.3.1
214 // doc.getElementByID
219 void xforms_propertyFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
221 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
222 xmlChar
* pString
= xmlXPathPopString(ctxt
);
223 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
224 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
225 if (aString
.equalsIgnoreAsciiCase("version"))
226 xmlXPathReturnString(ctxt
, reinterpret_cast<xmlChar
*>(const_cast<char *>("1.0")));
227 else if (aString
.equalsIgnoreAsciiCase("conformance-level"))
228 xmlXPathReturnString(ctxt
, reinterpret_cast<xmlChar
*>(const_cast<char *>("conformance")));
230 xmlXPathReturnEmptyString(ctxt
);
233 // Date and Time Functions
235 static OString
makeDateTimeString (const DateTime
& aDateTime
)
237 OStringBuffer aDateTimeString
;
238 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetYear()));
239 aDateTimeString
.append('-');
240 if (aDateTime
.GetMonth()<10) aDateTimeString
.append('0');
241 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetMonth()));
242 aDateTimeString
.append('-');
243 if (aDateTime
.GetDay()<10) aDateTimeString
.append('0');
244 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetDay()));
245 aDateTimeString
.append('T');
246 if (aDateTime
.GetHour()<10) aDateTimeString
.append('0');
247 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetHour()));
248 aDateTimeString
.append(':');
249 if (aDateTime
.GetMin()<10) aDateTimeString
.append('0');
250 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetMin()));
251 aDateTimeString
.append(':');
252 if (aDateTime
.GetSec()<10) aDateTimeString
.append('0');
253 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetSec()));
254 aDateTimeString
.append('Z');
256 return aDateTimeString
.makeStringAndClear();
259 // returns current system date and time in canonical xsd:dateTime
261 void xforms_nowFunction(xmlXPathParserContextPtr ctxt
, int /*nargs*/)
264 A single lexical representation, which is a subset of the lexical representations
265 allowed by [ISO 8601], is allowed for dateTime. This lexical representation is the
266 [ISO 8601] extended format CCYY-MM-DDThh:mm:ss where "CC" represents the century,
267 "YY" the year, "MM" the month and "DD" the day, preceded by an optional leading "-"
268 sign to indicate a negative number. If the sign is omitted, "+" is assumed. The letter
269 "T" is the date/time separator and "hh", "mm", "ss" represent hour, minute and second
274 3.2.7.2 Canonical representation
275 The canonical representation for dateTime is defined by prohibiting certain options
276 from the Lexical representation (par.3.2.7.1). Specifically, either the time zone must
277 be omitted or, if present, the time zone must be Coordinated Universal tools::Time (UTC)
280 DateTime
aDateTime( DateTime::SYSTEM
);
281 OString aDateTimeString
= makeDateTimeString(aDateTime
);
282 xmlChar
*pString
= static_cast<xmlChar
*>(xmlMalloc(aDateTimeString
.getLength()+1));
283 strncpy(reinterpret_cast<char*>(pString
), aDateTimeString
.getStr(), aDateTimeString
.getLength());
284 pString
[aDateTimeString
.getLength()] = 0;
285 xmlXPathReturnString(ctxt
, pString
);
288 static bool parseDateTime(std::u16string_view aString
, DateTime
& aDateTime
)
290 // take apart a canonical literal xsd:dateTime string
291 //CCYY-MM-DDThh:mm:ss(Z)
293 OUString
aDateTimeString( o3tl::trim(aString
) );
296 if (aDateTimeString
.getLength() < 19 || aDateTimeString
.getLength() > 20)
299 sal_Int32 nIndex
= 0;
300 sal_Int32 nYear
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, '-', nIndex
));
301 sal_Int32 nMonth
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, '-', nIndex
));
302 sal_Int32 nDay
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, 'T', nIndex
));
303 sal_Int32 nHour
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, ':', nIndex
));
304 sal_Int32 nMinute
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, ':', nIndex
));
305 sal_Int32 nSecond
= o3tl::toInt32(o3tl::getToken(aDateTimeString
, 0, 'Z', nIndex
));
307 Date
tmpDate(static_cast<sal_uInt16
>(nDay
), static_cast<sal_uInt16
>(nMonth
), static_cast<sal_uInt16
>(nYear
));
308 tools::Time
tmpTime(nHour
, nMinute
, nSecond
);
309 DateTime
tmpDateTime(tmpDate
, tmpTime
);
310 if (aString
.rfind('Z') == std::u16string_view::npos
)
311 tmpDateTime
.ConvertToUTC();
313 aDateTime
= tmpDateTime
;
319 void xforms_daysFromDateFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
321 // number of days from 1970-01-01 to supplied xsd:date(Time)
323 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
324 xmlChar
* pString
= xmlXPathPopString(ctxt
);
325 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
326 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
328 DateTime
aDateTime( DateTime::EMPTY
);
329 if (parseDateTime(aString
, aDateTime
))
331 Date
aReferenceDate(1, 1, 1970);
332 sal_Int32 nDays
= aDateTime
- aReferenceDate
;
333 xmlXPathReturnNumber(ctxt
, nDays
);
336 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
342 void xforms_secondsFromDateTimeFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
344 // number of seconds from 1970-01-01T00:00:00Z to supplied xsd:date(Time)
346 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
347 xmlChar
* pString
= xmlXPathPopString(ctxt
);
348 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
349 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
351 DateTime
aDateTime( DateTime::EMPTY
);
353 if (parseDateTime(aString
, aDateTime
))
355 Date
aReferenceDate(1, 1, 1970);
356 sal_Int32 nDays
= aDateTime
- aReferenceDate
;
357 sal_Int32 nSeconds
= nDays
* 24 * 60 * 60;
358 nSeconds
+= aDateTime
.GetHour() * 60 * 60;
359 nSeconds
+= aDateTime
.GetMin() * 60;
360 nSeconds
+= aDateTime
.GetSec();
361 xmlXPathReturnNumber(ctxt
, nSeconds
);
364 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
368 static bool parseDuration(const xmlChar
* aString
, bool& bNegative
, sal_Int32
& nYears
, sal_Int32
& nMonth
, sal_Int32
& nDays
,
369 sal_Int32
& nHours
, sal_Int32
& nMinutes
, sal_Int32
& nSeconds
)
371 bool bTime
= false; // in part after T
372 const xmlChar
*pString
= aString
;
374 if (pString
[0] == '-') {
379 if (pString
[0] != 'P')
385 const xmlChar
* pToken
= pString
;
386 while(pToken
[0] != 0)
390 nYears
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
395 nMonth
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
397 nMinutes
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
401 nDays
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
405 nHours
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
409 nSeconds
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
423 void xforms_secondsFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
425 // convert a xsd:duration to seconds
427 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
428 xmlChar
* pString
= xmlXPathPopString(ctxt
);
429 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
431 bool bNegative
= false;
432 sal_Int32 nYears
= 0;
433 sal_Int32 nMonths
= 0;
435 sal_Int32 nHours
= 0;
436 sal_Int32 nMinutes
= 0;
437 sal_Int32 nSeconds
= 0;
439 if (parseDuration(pString
, bNegative
, nYears
, nMonths
, nDays
, nHours
, nMinutes
, nSeconds
))
441 nSeconds
+= nMinutes
*60;
442 nSeconds
+= nHours
*60*60;
443 nSeconds
+= nDays
*24*60*60;
444 // year and month are ignored according to spec
446 nSeconds
= 0 - nSeconds
;
447 xmlXPathReturnNumber(ctxt
, nSeconds
);
450 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
453 void xforms_monthsFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
455 // convert a xsd:duration to seconds
457 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
458 xmlChar
* pString
= xmlXPathPopString(ctxt
);
459 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
461 bool bNegative
= false;
462 sal_Int32 nYears
= 0;
463 sal_Int32 nMonths
= 0;
465 sal_Int32 nHours
= 0;
466 sal_Int32 nMinutes
= 0;
467 sal_Int32 nSeconds
= 0;
469 if (parseDuration(pString
, bNegative
, nYears
, nMonths
, nDays
, nHours
, nMinutes
, nSeconds
))
471 nMonths
+= nYears
*12;
472 // Days, Hours, Minutes and seconds are ignored, see spec
474 nMonths
= 0 - nMonths
;
475 xmlXPathReturnNumber(ctxt
, nMonths
);
478 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
482 // Node-set Functions
483 void xforms_instanceFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
485 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
486 xmlChar
*pString
= xmlXPathPopString(ctxt
);
487 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
488 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
490 Reference
< XModel
> aModel
= static_cast<CLibxml2XFormsExtension
*>(ctxt
->context
->funcLookupData
)->getModel();
493 Reference
< XDocument
> aInstance
= aModel
->getInstanceDocument(aString
);
497 // xmlXPathObjectPtr xmlXPathNewNodeSet (xmlNodePtr val);
498 Reference
< XUnoTunnel
> aTunnel(aInstance
, UNO_QUERY_THROW
);
499 xmlNodePtr pNode
= comphelper::getSomething_cast
<xmlNode
>(aTunnel
->getSomething(Sequence
<sal_Int8
>()));
500 xmlXPathObjectPtr pObject
= xmlXPathNewNodeSet(pNode
);
501 xmlXPathReturnNodeSet(ctxt
, pObject
->nodesetval
);
502 } catch (const RuntimeException
&)
504 xmlXPathReturnEmptyNodeSet(ctxt
);
508 xmlXPathReturnEmptyNodeSet(ctxt
);
511 xmlXPathReturnEmptyNodeSet(ctxt
);
515 // Node-set Functions, XForms 1.1
516 void xforms_currentFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
518 if (nargs
!= 0) XP_ERROR(XPATH_INVALID_ARITY
);
520 Reference
< XNode
> aNode
= static_cast<CLibxml2XFormsExtension
*>(ctxt
->context
->funcLookupData
)->getContextNode();
525 Reference
< XUnoTunnel
> aTunnel(aNode
, UNO_QUERY_THROW
);
526 xmlNodePtr pNode
= comphelper::getSomething_cast
<xmlNode
>(aTunnel
->getSomething(Sequence
<sal_Int8
>()));
527 xmlXPathObjectPtr pObject
= xmlXPathNewNodeSet(pNode
);
528 xmlXPathReturnNodeSet(ctxt
, pObject
->nodesetval
);
530 catch (const RuntimeException
&)
532 xmlXPathReturnEmptyNodeSet(ctxt
);
536 xmlXPathReturnEmptyNodeSet(ctxt
);
539 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */