|
52 | 52 | import org.exist.xquery.Expression; |
53 | 53 | import org.exist.xquery.NameTest; |
54 | 54 | import org.exist.xquery.XPathException; |
| 55 | +import org.exist.xquery.XQueryContext; |
| 56 | +import org.exist.xquery.functions.array.ArrayType; |
55 | 57 | import org.exist.xquery.value.*; |
56 | 58 | import org.w3c.dom.Comment; |
57 | 59 | import org.w3c.dom.Document; |
|
74 | 76 | import javax.xml.xquery.XQItemType; |
75 | 77 | import java.io.Reader; |
76 | 78 | import java.io.StringReader; |
| 79 | +import java.util.ArrayList; |
| 80 | +import java.util.List; |
77 | 81 | import java.util.Properties; |
78 | 82 |
|
79 | 83 | import static org.exist.util.StringUtil.nullIfEmpty; |
@@ -106,7 +110,7 @@ public class Marshaller { |
106 | 110 |
|
107 | 111 | private final static String ATTR_NAME = "name"; |
108 | 112 |
|
109 | | - public final static QName ROOT_ELEMENT_QNAME = new QName(SEQ_ELEMENT, NAMESPACE, PREFIX); |
| 113 | + public final static QName SEQUENCE_ELEMENT_QNAME = new QName(SEQ_ELEMENT, NAMESPACE, PREFIX); |
110 | 114 |
|
111 | 115 | /** |
112 | 116 | * Marshall a sequence in an xml based string representation. |
@@ -253,135 +257,154 @@ public static Sequence demarshall(DBBroker broker, XMLStreamReader parser) throw |
253 | 257 | return result; |
254 | 258 | } |
255 | 259 |
|
256 | | - public static Sequence demarshall(final NodeImpl node) throws XMLStreamException, XPathException { |
| 260 | + public static Sequence demarshall(final XQueryContext context, final NodeImpl node) throws XMLStreamException, XPathException { |
| 261 | + return demarshallSequence(context, node); |
| 262 | + } |
| 263 | + |
| 264 | + private static Sequence demarshallSequence(final XQueryContext context, final NodeImpl node) throws XMLStreamException, XPathException { |
257 | 265 | final String ns = node.getNamespaceURI(); |
258 | 266 | if (ns == null || !NAMESPACE.equals(ns)) { |
259 | | - throw new XMLStreamException("Root element is not in the correct namespace. Expected: " + NAMESPACE); |
| 267 | + throw new XMLStreamException("Sequence element is not in the correct namespace. Expected: " + NAMESPACE); |
260 | 268 | } |
261 | 269 | if (!SEQ_ELEMENT.equals(node.getLocalName())) { |
262 | | - throw new XMLStreamException("Root element should be a " + SEQ_ELEMENT_QNAME); |
| 270 | + throw new XMLStreamException("Element should be a " + SEQ_ELEMENT_QNAME); |
263 | 271 | } |
| 272 | + |
| 273 | + return demarshallValues(context, node); |
| 274 | + } |
| 275 | + |
| 276 | + private static Sequence demarshallValues(final XQueryContext context, final NodeImpl node) throws XMLStreamException, XPathException { |
264 | 277 | final ValueSequence result = new ValueSequence(); |
265 | | - final InMemoryNodeSet values = new InMemoryNodeSet(); |
266 | | - node.selectChildren(new NameTest(Type.ELEMENT, VALUE_QNAME), values); |
267 | | - for (final SequenceIterator i = values.iterate(); i.hasNext();) { |
268 | | - final ElementImpl sxValue = (ElementImpl) i.nextItem(); |
269 | | - |
270 | | - int type = Type.ITEM; |
271 | | - final String typeName = sxValue.getAttribute(ATTR_TYPE); |
272 | | - if (!typeName.isEmpty()) { |
273 | | - type = Type.getType(typeName); |
274 | | - } |
| 278 | + final InMemoryNodeSet sxValues = new InMemoryNodeSet(); |
| 279 | + node.selectChildren(new NameTest(Type.ELEMENT, VALUE_QNAME), sxValues); |
| 280 | + for (final SequenceIterator itSxValue = sxValues.iterate(); itSxValue.hasNext();) { |
| 281 | + final ElementImpl sxValue = (ElementImpl) itSxValue.nextItem(); |
| 282 | + final Item item = demarshallValue(context, sxValue); |
| 283 | + result.add(item); |
| 284 | + } |
| 285 | + return result; |
| 286 | + } |
| 287 | + |
| 288 | + private static Item demarshallValue(final XQueryContext context, final ElementImpl sxValue) throws XMLStreamException, XPathException { |
| 289 | + int type = Type.ITEM; |
| 290 | + final String typeName = sxValue.getAttribute(ATTR_TYPE); |
| 291 | + if (!typeName.isEmpty()) { |
| 292 | + type = Type.getType(typeName); |
| 293 | + } |
| 294 | + |
| 295 | + @Nullable String attrNameString = null; |
| 296 | + if (sxValue instanceof Element) { |
| 297 | + attrNameString = nullIfEmpty(sxValue.getAttribute(ATTR_NAME)); |
| 298 | + } |
275 | 299 |
|
276 | | - // TODO(AR) coerce the item type to the type desired for the variable binding |
| 300 | + final InMemoryNodeSet sxSequences = new InMemoryNodeSet(); |
| 301 | + sxValue.selectChildren(new NameTest(Type.ELEMENT, SEQUENCE_ELEMENT_QNAME), sxSequences); |
277 | 302 |
|
278 | | - @Nullable String attrNameString = null; |
279 | | - if (sxValue instanceof Element) { |
280 | | - attrNameString = nullIfEmpty(sxValue.getAttribute(ATTR_NAME)); |
| 303 | + Node item = sxValue.getFirstChild(); |
| 304 | + |
| 305 | + if (type == Type.ATTRIBUTE || (type == Type.ITEM && attrNameString != null)) { |
| 306 | + if (attrNameString.isEmpty()) { |
| 307 | + throw new XMLStreamException("sx:value must contain a name attribute if type is " + typeName); |
281 | 308 | } |
282 | | - Node item = sxValue.getFirstChild(); |
283 | 309 |
|
284 | | - if (type == Type.ATTRIBUTE || (type == Type.ITEM && attrNameString != null)) { |
285 | | - if (attrNameString.isEmpty()) { |
286 | | - throw new XMLStreamException("sx:value must contain a name attribute if type is " + typeName); |
| 310 | + final String attrPrefix; |
| 311 | + final String attrNamespace; |
| 312 | + final String attrLocalName; |
| 313 | + final int colonSep = attrNameString.indexOf(':'); |
| 314 | + if (colonSep > -1) { |
| 315 | + attrPrefix = attrNameString.substring(0, colonSep); |
| 316 | + attrNamespace = item.lookupNamespaceURI(attrPrefix); |
| 317 | + if (attrNamespace == null) { |
| 318 | + throw new XMLStreamException("sx:value's name attribute contains the QName prefix '" + attrPrefix + "' for which the namespace has not been declared if type is " + typeName); |
287 | 319 | } |
| 320 | + attrLocalName = attrNameString.substring(colonSep + 1); |
| 321 | + } else { |
| 322 | + attrPrefix = XMLConstants.DEFAULT_NS_PREFIX; |
| 323 | + attrNamespace = XMLConstants.NULL_NS_URI; |
| 324 | + attrLocalName = attrNameString; |
| 325 | + } |
288 | 326 |
|
289 | | - final String attrPrefix; |
290 | | - final String attrNamespace; |
291 | | - final String attrLocalName; |
292 | | - final int colonSep = attrNameString.indexOf(':'); |
293 | | - if (colonSep > -1) { |
294 | | - attrPrefix = attrNameString.substring(0, colonSep); |
295 | | - attrNamespace = item.lookupNamespaceURI(attrPrefix); |
296 | | - if (attrNamespace == null) { |
297 | | - throw new XMLStreamException("sx:value's name attribute contains the QName prefix '" + attrPrefix + "' for which the namespace has not been declared if type is " + typeName); |
| 327 | + final QName attrName = new QName(attrLocalName, attrNamespace, attrPrefix, ElementValue.ATTRIBUTE); |
| 328 | + final MemTreeBuilder builder = new MemTreeBuilder(context); |
| 329 | + builder.startDocument(); |
| 330 | + final int attrNodeNumber = builder.addAttribute(attrName, sxValue.getTextContent()); |
| 331 | + builder.endDocument(); |
| 332 | + final AttrImpl attr = (AttrImpl) builder.getDocument().getAttribute(attrNodeNumber); |
| 333 | + return attr; |
| 334 | + |
| 335 | + } else if (Type.subTypeOf(type, Type.NODE)) { |
| 336 | + |
| 337 | + switch (type) { |
| 338 | + case Type.ELEMENT: |
| 339 | + if (item instanceof Document) { |
| 340 | + return (ElementImpl) ((DocumentImpl) item).getDocumentElement(); |
| 341 | + } else if (!(item instanceof Element)) { |
| 342 | + throw new XMLStreamException("sx:value should only contain an Element if type is " + typeName); |
| 343 | + } else { |
| 344 | + return (ElementImpl) item; |
298 | 345 | } |
299 | | - attrLocalName = attrNameString.substring(colonSep + 1); |
300 | | - } else { |
301 | | - attrPrefix = XMLConstants.DEFAULT_NS_PREFIX; |
302 | | - attrNamespace = XMLConstants.NULL_NS_URI; |
303 | | - attrLocalName = attrNameString; |
304 | | - } |
305 | 346 |
|
306 | | - final QName attrName = new QName(attrLocalName, attrNamespace, attrPrefix, ElementValue.ATTRIBUTE); |
307 | | - final MemTreeBuilder builder = new MemTreeBuilder(); |
308 | | - builder.startDocument(); |
309 | | - final int attrNodeNumber = builder.addAttribute(attrName, sxValue.getTextContent()); |
310 | | - builder.endDocument(); |
311 | | - final AttrImpl attr = (AttrImpl) builder.getDocument().getAttribute(attrNodeNumber); |
312 | | - result.add(attr); |
313 | | - |
314 | | - } else if (Type.subTypeOf(type, Type.NODE)) { |
315 | | - |
316 | | - switch (type) { |
317 | | - case Type.ELEMENT: |
318 | | - if (item instanceof Document) { |
319 | | - result.add((ElementImpl) ((DocumentImpl) item).getDocumentElement()); |
320 | | - } else if (!(item instanceof Element)) { |
321 | | - throw new XMLStreamException("sx:value should only contain an Element if type is " + typeName); |
322 | | - } else { |
323 | | - result.add((ElementImpl) item); |
324 | | - } |
325 | | - break; |
326 | | - |
327 | | - case Type.COMMENT: |
328 | | - if (!(item instanceof Comment)) { |
329 | | - throw new XMLStreamException("sx:value should only contain a Comment node if type is " + typeName); |
330 | | - } |
331 | | - result.add((CommentImpl) item); |
332 | | - break; |
| 347 | + case Type.COMMENT: |
| 348 | + if (!(item instanceof Comment)) { |
| 349 | + throw new XMLStreamException("sx:value should only contain a Comment node if type is " + typeName); |
| 350 | + } |
| 351 | + return (CommentImpl) item; |
333 | 352 |
|
334 | | - case Type.PROCESSING_INSTRUCTION: |
335 | | - if (!(item instanceof ProcessingInstruction)) { |
336 | | - throw new XMLStreamException("sx:value should only contain a Processing Instruction node if type is " + typeName); |
337 | | - } |
338 | | - result.add((ProcessingInstructionImpl) item); |
339 | | - break; |
| 353 | + case Type.PROCESSING_INSTRUCTION: |
| 354 | + if (!(item instanceof ProcessingInstruction)) { |
| 355 | + throw new XMLStreamException("sx:value should only contain a Processing Instruction node if type is " + typeName); |
| 356 | + } |
| 357 | + return (ProcessingInstructionImpl) item; |
340 | 358 |
|
341 | | - case Type.TEXT: |
342 | | - if (!(item instanceof Text)) { |
343 | | - throw new XMLStreamException("sx:value should only contain a Text node if type is " + typeName); |
344 | | - } |
345 | | - result.add((TextImpl) item); |
346 | | - break; |
347 | | - |
348 | | - case Type.DOCUMENT: |
349 | | - default: |
350 | | - if (item instanceof Document || item instanceof Element) { |
351 | | - final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(((NodeImpl) item).getExpression()); |
352 | | - try { |
353 | | - receiver.startDocument(); |
354 | | - ((NodeImpl) item).copyTo(null, receiver); |
355 | | - receiver.endDocument(); |
356 | | - } catch (final SAXException e) { |
357 | | - throw new XPathException(item != null ? ((NodeImpl) item).getExpression() : null, "Error while demarshalling node: " + e.getMessage(), e); |
358 | | - } |
359 | | - result.add((NodeImpl) receiver.getDocument()); |
360 | | - } else { |
361 | | - throw new XMLStreamException("sx:value should only contain a Node if type is " + typeName); |
| 359 | + case Type.TEXT: |
| 360 | + if (!(item instanceof Text)) { |
| 361 | + throw new XMLStreamException("sx:value should only contain a Text node if type is " + typeName); |
| 362 | + } |
| 363 | + return (TextImpl) item; |
| 364 | + |
| 365 | + case Type.DOCUMENT: |
| 366 | + default: |
| 367 | + if (item instanceof Document || item instanceof Element) { |
| 368 | + final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(((NodeImpl) item).getExpression()); |
| 369 | + try { |
| 370 | + receiver.startDocument(); |
| 371 | + ((NodeImpl) item).copyTo(null, receiver); |
| 372 | + receiver.endDocument(); |
| 373 | + } catch (final SAXException e) { |
| 374 | + throw new XPathException(item != null ? ((NodeImpl) item).getExpression() : null, "Error while demarshalling node: " + e.getMessage(), e); |
362 | 375 | } |
363 | | - break; |
364 | | - } |
| 376 | + return (NodeImpl) receiver.getDocument(); |
| 377 | + } else { |
| 378 | + throw new XMLStreamException("sx:value should only contain a Node if type is " + typeName); |
| 379 | + } |
| 380 | + } |
365 | 381 |
|
366 | | - } else if (type == Type.ITEM && !(item instanceof Text)) { |
367 | | - // item() type requested and we have been given a node which is not a text() node |
368 | | - result.add((NodeImpl) item); |
| 382 | + } else if (type == Type.ITEM && !(item instanceof Text)) { |
| 383 | + // item() type requested and we have been given a node which is not a text() node |
| 384 | + return (NodeImpl) item; |
| 385 | + |
| 386 | + } else if (type == Type.ARRAY_ITEM || (type == Type.ITEM && !sxSequences.isEmpty())) { |
| 387 | + // array(*) type |
| 388 | + final List<Sequence> arrayValues = new ArrayList<>(); |
| 389 | + for (final SequenceIterator itSxSequence = sxSequences.iterate(); itSxSequence.hasNext();) { |
| 390 | + final ElementImpl sxSequence = (ElementImpl) itSxSequence.nextItem(); |
| 391 | + final Sequence arrayValue = demarshallSequence(context, sxSequence); |
| 392 | + arrayValues.add(arrayValue); |
| 393 | + } |
| 394 | + return new ArrayType(context, arrayValues); |
369 | 395 |
|
370 | | - } else { |
371 | | - // specific non-node type or text() |
372 | | - final StringBuilder data = new StringBuilder(); |
373 | | - while (item != null) { |
374 | | - if (!(item.getNodeType() == Node.TEXT_NODE || item.getNodeType() == Node.CDATA_SECTION_NODE)) { |
375 | | - throw new XMLStreamException("sx:value should only contain text if type is " + typeName); |
376 | | - } |
377 | | - data.append(item.getNodeValue()); |
378 | | - item = item.getNextSibling(); |
| 396 | + } else { |
| 397 | + // specific non-node type or text() |
| 398 | + final StringBuilder data = new StringBuilder(); |
| 399 | + while (item != null) { |
| 400 | + if (!(item.getNodeType() == Node.TEXT_NODE || item.getNodeType() == Node.CDATA_SECTION_NODE)) { |
| 401 | + throw new XMLStreamException("sx:value should only contain text if type is " + typeName); |
379 | 402 | } |
380 | | - result.add(new StringValue(data.toString()).convertTo(type)); |
| 403 | + data.append(item.getNodeValue()); |
| 404 | + item = item.getNextSibling(); |
381 | 405 | } |
| 406 | + return new StringValue(data.toString()).convertTo(type); |
382 | 407 | } |
383 | | - |
384 | | - return result; |
385 | 408 | } |
386 | 409 |
|
387 | 410 | public static Item streamToDOM(XMLStreamReader parser, XQItemType type) throws XMLStreamException, XQException { |
|
0 commit comments