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 .
22 #include <sal/types.h>
23 #include <rtl/ustring.hxx>
24 #include <rtl/string.hxx>
25 #include <rtl/strbuf.hxx>
26 #include <tools/date.hxx>
27 #include <tools/time.hxx>
28 #include <tools/datetime.hxx>
30 #include <com/sun/star/uno/Reference.hxx>
31 #include <com/sun/star/uno/Sequence.hxx>
32 #include <com/sun/star/xforms/XModel.hpp>
33 #include <com/sun/star/xml/dom/XNode.hpp>
34 #include <com/sun/star/xml/dom/XDocument.hpp>
35 #include <com/sun/star/lang/XUnoTunnel.hpp>
37 #include <boost/lexical_cast.hpp>
38 #include <libxml/xpathInternals.h>
40 #include "xpathlib.hxx"
41 #include "extension.hxx"
45 using namespace com::sun::star::uno
;
46 using namespace com::sun::star::xml::dom
;
47 using namespace com::sun::star::xforms
;
48 using namespace com::sun::star::lang
;
50 xmlXPathFunction
xforms_lookupFunc(void *, const xmlChar
*xname
, const xmlChar
*)
53 const char *name
= reinterpret_cast<char const *>(xname
);
54 if (strcmp("boolean-from-string", name
)==0)
55 return xforms_booleanFromStringFunction
;
56 else if ((strcmp("if", name
))==0)
57 return xforms_ifFunction
;
58 else if ((strcmp("avg", name
))==0)
59 return xforms_avgFunction
;
60 else if ((strcmp("min", name
))==0)
61 return xforms_minFunction
;
62 else if ((strcmp("max", name
))==0)
63 return xforms_maxFunction
;
64 else if ((strcmp("count-non-empty", name
))==0)
65 return xforms_countNonEmptyFunction
;
66 else if ((strcmp("index", name
))==0)
67 return xforms_indexFunction
;
68 else if ((strcmp("property", name
))==0)
69 return xforms_propertyFunction
;
70 else if ((strcmp("now", name
))==0)
71 return xforms_nowFunction
;
72 else if ((strcmp("days-from-date", name
))==0)
73 return xforms_daysFromDateFunction
;
74 else if ((strcmp("seconds-from-dateTime", name
))==0)
75 return xforms_secondsFromDateTimeFunction
;
76 else if ((strcmp("seconds", name
))==0)
77 return xforms_secondsFunction
;
78 else if ((strcmp("months", name
))==0)
79 return xforms_monthsFunction
;
80 else if ((strcmp("instance", name
))==0)
81 return xforms_instanceFunction
;
82 else if ((strcmp("current", name
))==0)
83 return xforms_currentFunction
;
89 void xforms_booleanFromStringFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
91 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
92 xmlChar
*pString
= xmlXPathPopString(ctxt
);
93 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
94 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
95 if (aString
.equalsIgnoreAsciiCase("true") ||
96 aString
.equalsIgnoreAsciiCase("1"))
97 xmlXPathReturnTrue(ctxt
);
98 else if (aString
.equalsIgnoreAsciiCase("false") ||
99 aString
.equalsIgnoreAsciiCase("0"))
100 xmlXPathReturnFalse(ctxt
);
102 XP_ERROR(XPATH_NUMBER_ERROR
);
105 void xforms_ifFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
107 if (nargs
!= 3) XP_ERROR(XPATH_INVALID_ARITY
);
108 xmlChar
*s2
= xmlXPathPopString(ctxt
);
110 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
111 xmlChar
*s1
= xmlXPathPopString(ctxt
);
112 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
113 bool aBool
= xmlXPathPopBoolean(ctxt
);
114 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
117 xmlXPathReturnString(ctxt
, s1
);
119 xmlXPathReturnString(ctxt
, s2
);
124 void xforms_avgFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
126 // use sum(), div() and count()
127 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
130 xmlXPathObjectPtr pObject
= valuePop(ctxt
);
131 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
133 valuePush(ctxt
, xmlXPathObjectCopy(pObject
));
135 xmlXPathSumFunction(ctxt
, 1);
136 double nSum
= xmlXPathPopNumber(ctxt
);
137 // push a copy once more
138 valuePush(ctxt
, xmlXPathObjectCopy(pObject
));
139 xmlXPathCountFunction(ctxt
, 1);
140 double nCount
= xmlXPathPopNumber(ctxt
);
141 // push args for div()
142 xmlXPathReturnNumber(ctxt
, nSum
);
143 xmlXPathReturnNumber(ctxt
, nCount
);
144 xmlXPathDivValues(ctxt
);
145 // the result is now on the ctxt stack
146 xmlXPathFreeObject(pObject
);
149 void xforms_minFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
151 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
152 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
153 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
155 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
157 double nNumber
= xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet
, i
));
158 if (xmlXPathIsNaN(nNumber
))
160 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
165 else if (nNumber
< nMinimum
)
168 xmlXPathReturnNumber(ctxt
, nMinimum
);
171 void xforms_maxFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
173 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
174 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
175 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
177 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
179 double nNumber
= xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet
, i
));
180 if (xmlXPathIsNaN(nNumber
))
182 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
187 else if (nNumber
> nMaximum
)
190 xmlXPathReturnNumber(ctxt
, nMaximum
);
192 void xforms_countNonEmptyFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
194 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
195 xmlNodeSetPtr pNodeSet
= xmlXPathPopNodeSet(ctxt
);
196 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
197 sal_Int32 nNotEmpty
= 0;
198 for (int i
= 0; i
< xmlXPathNodeSetGetLength(pNodeSet
); i
++)
200 const xmlChar
*aString
= xmlXPathCastNodeToString(xmlXPathNodeSetItem(pNodeSet
, i
));
201 if (*aString
!= 0) nNotEmpty
++;
203 xmlXPathReturnNumber(ctxt
, nNotEmpty
);
205 void xforms_indexFunction(xmlXPathParserContextPtr
/*ctxt*/, int /*nargs*/)
207 // function index takes a string argument that is the IDREF of a
208 // 'repeat' and returns the current 1-based position of the repeat
209 // index of the identified repeat -- see xforms/9.3.1
211 // doc.getElementByID
216 void xforms_propertyFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
218 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
219 xmlChar
* pString
= xmlXPathPopString(ctxt
);
220 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
221 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
222 if (aString
.equalsIgnoreAsciiCase("version"))
223 xmlXPathReturnString(ctxt
, reinterpret_cast<xmlChar
*>(const_cast<char *>("1.0")));
224 else if (aString
.equalsIgnoreAsciiCase("conformance-level"))
225 xmlXPathReturnString(ctxt
, reinterpret_cast<xmlChar
*>(const_cast<char *>("conformance")));
227 xmlXPathReturnEmptyString(ctxt
);
230 // Date and Time Functions
232 static OString
makeDateTimeString (const DateTime
& aDateTime
)
234 OStringBuffer aDateTimeString
;
235 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetYear()));
236 aDateTimeString
.append('-');
237 if (aDateTime
.GetMonth()<10) aDateTimeString
.append('0');
238 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetMonth()));
239 aDateTimeString
.append('-');
240 if (aDateTime
.GetDay()<10) aDateTimeString
.append('0');
241 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetDay()));
242 aDateTimeString
.append('T');
243 if (aDateTime
.GetHour()<10) aDateTimeString
.append('0');
244 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetHour()));
245 aDateTimeString
.append(':');
246 if (aDateTime
.GetMin()<10) aDateTimeString
.append('0');
247 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetMin()));
248 aDateTimeString
.append(':');
249 if (aDateTime
.GetSec()<10) aDateTimeString
.append('0');
250 aDateTimeString
.append(static_cast<sal_Int32
>(aDateTime
.GetSec()));
251 aDateTimeString
.append('Z');
253 return aDateTimeString
.makeStringAndClear();
256 // returns current system date and time in canonical xsd:dateTime
258 void xforms_nowFunction(xmlXPathParserContextPtr ctxt
, int /*nargs*/)
261 A single lexical representation, which is a subset of the lexical representations
262 allowed by [ISO 8601], is allowed for dateTime. This lexical representation is the
263 [ISO 8601] extended format CCYY-MM-DDThh:mm:ss where "CC" represents the century,
264 "YY" the year, "MM" the month and "DD" the day, preceded by an optional leading "-"
265 sign to indicate a negative number. If the sign is omitted, "+" is assumed. The letter
266 "T" is the date/time separator and "hh", "mm", "ss" represent hour, minute and second
271 3.2.7.2 Canonical representation
272 The canonical representation for dateTime is defined by prohibiting certain options
273 from the Lexical representation (par.3.2.7.1). Specifically, either the time zone must
274 be omitted or, if present, the time zone must be Coordinated Universal tools::Time (UTC)
277 DateTime
aDateTime( DateTime::SYSTEM
);
278 OString aDateTimeString
= makeDateTimeString(aDateTime
);
279 xmlChar
*pString
= static_cast<xmlChar
*>(xmlMalloc(aDateTimeString
.getLength()+1));
280 strncpy(reinterpret_cast<char*>(pString
), aDateTimeString
.getStr(), aDateTimeString
.getLength());
281 pString
[aDateTimeString
.getLength()] = 0;
282 xmlXPathReturnString(ctxt
, pString
);
285 static bool parseDateTime(const OUString
& aString
, DateTime
& aDateTime
)
287 // take apart a canonical literal xsd:dateTime string
288 //CCYY-MM-DDThh:mm:ss(Z)
290 OUString aDateTimeString
= aString
.trim();
293 if (aDateTimeString
.getLength() < 19 || aDateTimeString
.getLength() > 20)
296 sal_Int32 nIndex
= 0;
297 sal_Int32 nYear
= aDateTimeString
.getToken(0, '-', nIndex
).toInt32();
298 sal_Int32 nMonth
= aDateTimeString
.getToken(0, '-', nIndex
).toInt32();
299 sal_Int32 nDay
= aDateTimeString
.getToken(0, 'T', nIndex
).toInt32();
300 sal_Int32 nHour
= aDateTimeString
.getToken(0, ':', nIndex
).toInt32();
301 sal_Int32 nMinute
= aDateTimeString
.getToken(0, ':', nIndex
).toInt32();
302 sal_Int32 nSecond
= aDateTimeString
.getToken(0, 'Z', nIndex
).toInt32();
304 Date
tmpDate(static_cast<sal_uInt16
>(nDay
), static_cast<sal_uInt16
>(nMonth
), static_cast<sal_uInt16
>(nYear
));
305 tools::Time
tmpTime(nHour
, nMinute
, nSecond
);
306 DateTime
tmpDateTime(tmpDate
, tmpTime
);
307 if (aString
.lastIndexOf('Z') < 0)
308 tmpDateTime
.ConvertToUTC();
310 aDateTime
= tmpDateTime
;
316 void xforms_daysFromDateFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
318 // number of days from 1970-01-01 to supplied xsd:date(Time)
320 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
321 xmlChar
* pString
= xmlXPathPopString(ctxt
);
322 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
323 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
325 DateTime
aDateTime( DateTime::EMPTY
);
326 if (parseDateTime(aString
, aDateTime
))
328 Date
aReferenceDate(1, 1, 1970);
329 sal_Int32 nDays
= aDateTime
- aReferenceDate
;
330 xmlXPathReturnNumber(ctxt
, nDays
);
333 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
339 void xforms_secondsFromDateTimeFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
341 // number of seconds from 1970-01-01T00:00:00Z to supplied xsd:date(Time)
343 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
344 xmlChar
* pString
= xmlXPathPopString(ctxt
);
345 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
346 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
348 DateTime
aDateTime( DateTime::EMPTY
);
350 if (parseDateTime(aString
, aDateTime
))
352 Date
aReferenceDate(1, 1, 1970);
353 sal_Int32 nDays
= aDateTime
- aReferenceDate
;
354 sal_Int32 nSeconds
= nDays
* 24 * 60 * 60;
355 nSeconds
+= aDateTime
.GetHour() * 60 * 60;
356 nSeconds
+= aDateTime
.GetMin() * 60;
357 nSeconds
+= aDateTime
.GetSec();
358 xmlXPathReturnNumber(ctxt
, nSeconds
);
361 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
365 static bool parseDuration(const xmlChar
* aString
, bool& bNegative
, sal_Int32
& nYears
, sal_Int32
& nMonth
, sal_Int32
& nDays
,
366 sal_Int32
& nHours
, sal_Int32
& nMinutes
, sal_Int32
& nSeconds
)
368 bool bTime
= false; // in part after T
369 const xmlChar
*pString
= aString
;
371 if (pString
[0] == '-') {
376 if (pString
[0] != 'P')
382 const xmlChar
* pToken
= pString
;
383 while(pToken
[0] != 0)
387 nYears
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
392 nMonth
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
394 nMinutes
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
398 nDays
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
402 nHours
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
406 nSeconds
= boost::lexical_cast
<sal_Int32
>(pString
, pString
-pToken
);
420 void xforms_secondsFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
422 // convert a xsd:duration to seconds
424 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
425 xmlChar
* pString
= xmlXPathPopString(ctxt
);
426 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
428 bool bNegative
= false;
429 sal_Int32 nYears
= 0;
430 sal_Int32 nMonths
= 0;
432 sal_Int32 nHours
= 0;
433 sal_Int32 nMinutes
= 0;
434 sal_Int32 nSeconds
= 0;
436 if (parseDuration(pString
, bNegative
, nYears
, nMonths
, nDays
, nHours
, nMinutes
, nSeconds
))
438 nSeconds
+= nMinutes
*60;
439 nSeconds
+= nHours
*60*60;
440 nSeconds
+= nDays
*24*60*60;
441 // year and month are ignored according to spec
443 nSeconds
= 0 - nSeconds
;
444 xmlXPathReturnNumber(ctxt
, nSeconds
);
447 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
450 void xforms_monthsFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
452 // convert a xsd:duration to seconds
454 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
455 xmlChar
* pString
= xmlXPathPopString(ctxt
);
456 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
458 bool bNegative
= false;
459 sal_Int32 nYears
= 0;
460 sal_Int32 nMonths
= 0;
462 sal_Int32 nHours
= 0;
463 sal_Int32 nMinutes
= 0;
464 sal_Int32 nSeconds
= 0;
466 if (parseDuration(pString
, bNegative
, nYears
, nMonths
, nDays
, nHours
, nMinutes
, nSeconds
))
468 nMonths
+= nYears
*12;
469 // Days, Hours, Minutes and seconds are ignored, see spec
471 nMonths
= 0 - nMonths
;
472 xmlXPathReturnNumber(ctxt
, nMonths
);
475 xmlXPathReturnNumber(ctxt
, xmlXPathNAN
);
479 // Node-set Functions
480 void xforms_instanceFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
482 if (nargs
!= 1) XP_ERROR(XPATH_INVALID_ARITY
);
483 xmlChar
*pString
= xmlXPathPopString(ctxt
);
484 if (xmlXPathCheckError(ctxt
)) XP_ERROR(XPATH_INVALID_TYPE
);
485 OUString
aString(reinterpret_cast<char*>(pString
), strlen(reinterpret_cast<char*>(pString
)), RTL_TEXTENCODING_UTF8
);
487 Reference
< XModel
> aModel
= static_cast<CLibxml2XFormsExtension
*>(ctxt
->context
->funcLookupData
)->getModel();
490 Reference
< XDocument
> aInstance
= aModel
->getInstanceDocument(aString
);
494 // xmlXPathObjectPtr xmlXPathNewNodeSet (xmlNodePtr val);
495 Reference
< XUnoTunnel
> aTunnel(aInstance
, UNO_QUERY_THROW
);
496 xmlNodePtr pNode
= reinterpret_cast< xmlNodePtr
>( aTunnel
->getSomething(Sequence
< sal_Int8
>()) );
497 xmlXPathObjectPtr pObject
= xmlXPathNewNodeSet(pNode
);
498 xmlXPathReturnNodeSet(ctxt
, pObject
->nodesetval
);
499 } catch (const RuntimeException
&)
501 xmlXPathReturnEmptyNodeSet(ctxt
);
505 xmlXPathReturnEmptyNodeSet(ctxt
);
508 xmlXPathReturnEmptyNodeSet(ctxt
);
512 // Node-set Functions, XForms 1.1
513 void xforms_currentFunction(xmlXPathParserContextPtr ctxt
, int nargs
)
515 if (nargs
!= 0) XP_ERROR(XPATH_INVALID_ARITY
);
517 Reference
< XNode
> aNode
= static_cast<CLibxml2XFormsExtension
*>(ctxt
->context
->funcLookupData
)->getContextNode();
522 Reference
< XUnoTunnel
> aTunnel(aNode
, UNO_QUERY_THROW
);
523 xmlNodePtr pNode
= reinterpret_cast< xmlNodePtr
>( aTunnel
->getSomething(Sequence
< sal_Int8
>()) );
524 xmlXPathObjectPtr pObject
= xmlXPathNewNodeSet(pNode
);
525 xmlXPathReturnNodeSet(ctxt
, pObject
->nodesetval
);
527 catch (const RuntimeException
&)
529 xmlXPathReturnEmptyNodeSet(ctxt
);
533 xmlXPathReturnEmptyNodeSet(ctxt
);
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */