1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtXmlPatterns module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QStack> |
41 | #include <QStringList> |
42 | |
43 | #include "qanyuri_p.h" |
44 | #include "qboolean_p.h" |
45 | #include "qcommonsequencetypes_p.h" |
46 | #include "qcommonvalues_p.h" |
47 | #include "qemptysequence_p.h" |
48 | #include "qitemmappingiterator_p.h" |
49 | #include "qnodesort_p.h" |
50 | #include "qpatternistlocale_p.h" |
51 | #include "private/qxmlutils_p.h" |
52 | |
53 | #include "qsequencegeneratingfns_p.h" |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | using namespace QPatternist; |
58 | |
59 | IdFN::IdFN() : m_hasCreatedSorter(false) |
60 | { |
61 | } |
62 | |
63 | Item IdFN::mapToItem(const QString &id, |
64 | const IDContext &context) const |
65 | { |
66 | return context.second->elementById(NCName: context.first->namePool()->allocateQName(uri: QString(), localName: id)); |
67 | } |
68 | |
69 | /** |
70 | * @short Helper class for IdFN. |
71 | * |
72 | * StringSplitter takes an Iterator which delivers strings of this kind: |
73 | * |
74 | * "a", "b c", "%invalidNCName", " ", "d" |
75 | * |
76 | * and we deliver instead: |
77 | * |
78 | * "a", "b", "c", "d" |
79 | * |
80 | * That is, we: |
81 | * - Remove invalid @c NCName |
82 | * - Split IDREFs into individual NCNames |
83 | * |
84 | * @author Frans Englich <frans.englich@nokia.com> |
85 | */ |
86 | class StringSplitter : public QAbstractXmlForwardIterator<QString> |
87 | { |
88 | public: |
89 | StringSplitter(const Item::Iterator::Ptr &source); |
90 | virtual QString next(); |
91 | virtual QString current() const; |
92 | virtual qint64 position() const; |
93 | private: |
94 | QString loadNext(); |
95 | const Item::Iterator::Ptr m_source; |
96 | QStack<QString> m_buffer; |
97 | QString m_current; |
98 | qint64 m_position; |
99 | bool m_sourceAtEnd; |
100 | }; |
101 | |
102 | StringSplitter::StringSplitter(const Item::Iterator::Ptr &source) : m_source(source) |
103 | , m_position(0) |
104 | , m_sourceAtEnd(false) |
105 | { |
106 | Q_ASSERT(m_source); |
107 | m_buffer.push(t: loadNext()); |
108 | } |
109 | |
110 | QString StringSplitter::next() |
111 | { |
112 | /* We also check m_position, we want to load on our first run. */ |
113 | if(!m_buffer.isEmpty()) |
114 | { |
115 | ++m_position; |
116 | m_current = m_buffer.pop(); |
117 | return m_current; |
118 | } |
119 | else if(m_sourceAtEnd) |
120 | { |
121 | m_current.clear(); |
122 | m_position = -1; |
123 | return QString(); |
124 | } |
125 | |
126 | return loadNext(); |
127 | } |
128 | |
129 | QString StringSplitter::loadNext() |
130 | { |
131 | const Item sourceNext(m_source->next()); |
132 | |
133 | if(sourceNext.isNull()) |
134 | { |
135 | m_sourceAtEnd = true; |
136 | /* We might have strings in m_buffer, let's empty it. */ |
137 | return next(); |
138 | } |
139 | |
140 | const QStringList candidates(sourceNext.stringValue().simplified().split(sep: QLatin1Char(' '))); |
141 | const int count = candidates.length(); |
142 | |
143 | for(int i = 0; i < count; ++i) |
144 | { |
145 | const QString &at = candidates.at(i); |
146 | |
147 | if(QXmlUtils::isNCName(ncName: at)) |
148 | m_buffer.push(t: at); |
149 | } |
150 | |
151 | /* So, now we have populated m_buffer, let's start from the beginning. */ |
152 | return next(); |
153 | } |
154 | |
155 | QString StringSplitter::current() const |
156 | { |
157 | return m_current; |
158 | } |
159 | |
160 | qint64 StringSplitter::position() const |
161 | { |
162 | return m_position; |
163 | } |
164 | |
165 | Item::Iterator::Ptr IdFN::evaluateSequence(const DynamicContext::Ptr &context) const |
166 | { |
167 | const Item::Iterator::Ptr idrefs(m_operands.first()->evaluateSequence(context)); |
168 | const Item node(m_operands.last()->evaluateSingleton(context)); |
169 | |
170 | checkTargetNode(node: node.asNode(), context, ReportContext::FODC0001); |
171 | |
172 | return makeItemMappingIterator<Item, |
173 | QString, |
174 | IdFN::ConstPtr, |
175 | IDContext>(mapper: ConstPtr(this), |
176 | source: StringSplitter::Ptr(new StringSplitter(idrefs)), |
177 | context: qMakePair(x: context, y: node.asNode().model())); |
178 | } |
179 | |
180 | Expression::Ptr IdFN::typeCheck(const StaticContext::Ptr &context, |
181 | const SequenceType::Ptr &reqType) |
182 | { |
183 | if(m_hasCreatedSorter) |
184 | return FunctionCall::typeCheck(context, reqType); |
185 | else |
186 | { |
187 | const Expression::Ptr newMe(new NodeSortExpression(Expression::Ptr(this))); |
188 | context->wrapExpressionWith(existingNode: this, newNode: newMe); |
189 | m_hasCreatedSorter = true; |
190 | return newMe->typeCheck(context, reqType); |
191 | } |
192 | } |
193 | |
194 | Item::Iterator::Ptr IdrefFN::evaluateSequence(const DynamicContext::Ptr &context) const |
195 | { |
196 | const Item::Iterator::Ptr ids(m_operands.first()->evaluateSequence(context)); |
197 | |
198 | Item mId(ids->next()); |
199 | if(!mId) |
200 | return CommonValues::emptyIterator; |
201 | |
202 | const Item node(m_operands.last()->evaluateSingleton(context)); |
203 | checkTargetNode(node: node.asNode(), context, ReportContext::FODC0001); |
204 | |
205 | return CommonValues::emptyIterator; /* TODO Haven't implemented further. */ |
206 | } |
207 | |
208 | Item DocFN::evaluateSingleton(const DynamicContext::Ptr &context) const |
209 | { |
210 | const Item itemURI(m_operands.first()->evaluateSingleton(context)); |
211 | |
212 | if(!itemURI) |
213 | return Item(); |
214 | |
215 | /* These two lines were previously in a separate function but are now duplicated |
216 | * in DocAvailableFN::evaluateEBV() and DocFN::typeCheck(), |
217 | * as part of a workaround for solaris-cc-64. DocFN::typeCheck() is in qsequencefns.cpp |
218 | * as part of that workaround. */ |
219 | const QUrl mayRela(AnyURI::toQUrl<ReportContext::FODC0005>(value: itemURI.stringValue(), context, r: this)); |
220 | const QUrl uri(context->resolveURI(relative: mayRela, baseURI: staticBaseURI())); |
221 | |
222 | Q_ASSERT(uri.isValid()); |
223 | Q_ASSERT(!uri.isRelative()); |
224 | |
225 | const Item doc(context->resourceLoader()->openDocument(uri, context)); |
226 | |
227 | return doc; |
228 | } |
229 | |
230 | SequenceType::Ptr DocFN::staticType() const |
231 | { |
232 | if(m_type) |
233 | return m_type; |
234 | else |
235 | return CommonSequenceTypes::ZeroOrOneDocumentNode; |
236 | } |
237 | |
238 | bool DocAvailableFN::evaluateEBV(const DynamicContext::Ptr &context) const |
239 | { |
240 | const Item itemURI(m_operands.first()->evaluateSingleton(context)); |
241 | |
242 | /* 15.5.4 fn:doc reads: "If $uri is the empty sequence, the result is an empty sequence." |
243 | * Hence, we return false for the empty sequence, because this doesn't hold true: |
244 | * "If this function returns true, then calling fn:doc($uri) within |
245 | * the same execution scope must return a document node."(15.5.5 fn:doc-available) */ |
246 | if(!itemURI) |
247 | return false; |
248 | |
249 | /* These two lines are duplicated in DocFN::evaluateSingleton(), as part |
250 | * of a workaround for solaris-cc-64. */ |
251 | const QUrl mayRela(AnyURI::toQUrl<ReportContext::FODC0005>(value: itemURI.stringValue(), context, r: this)); |
252 | const QUrl uri(context->resolveURI(relative: mayRela, baseURI: staticBaseURI())); |
253 | |
254 | Q_ASSERT(!uri.isRelative()); |
255 | return context->resourceLoader()->isDocumentAvailable(uri); |
256 | } |
257 | |
258 | Item::Iterator::Ptr CollectionFN::evaluateSequence(const DynamicContext::Ptr &context) const |
259 | { |
260 | // TODO resolve with URI resolve |
261 | if(m_operands.isEmpty()) |
262 | { |
263 | // TODO check default collection |
264 | context->error(message: QtXmlPatterns::tr(sourceText: "The default collection is undefined" ), |
265 | errorCode: ReportContext::FODC0002, reflection: this); |
266 | return CommonValues::emptyIterator; |
267 | } |
268 | else |
269 | { |
270 | const Item itemURI(m_operands.first()->evaluateSingleton(context)); |
271 | |
272 | if(itemURI) |
273 | { |
274 | const QUrl uri(AnyURI::toQUrl<ReportContext::FODC0004>(value: itemURI.stringValue(), context, r: this)); |
275 | |
276 | // TODO 2. Resolve against static context base URI(store base URI at compile time) |
277 | context->error(message: QtXmlPatterns::tr(sourceText: "%1 cannot be retrieved" ).arg(a: formatResourcePath(uri)), |
278 | errorCode: ReportContext::FODC0004, reflection: this); |
279 | return CommonValues::emptyIterator; |
280 | } |
281 | else |
282 | { |
283 | /* This is out default collection currently, */ |
284 | return CommonValues::emptyIterator; |
285 | } |
286 | } |
287 | } |
288 | |
289 | QT_END_NAMESPACE |
290 | |