• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

49.81
/exist-core/src/main/java/org/exist/xqj/Marshaller.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xqj;
50

51
import org.exist.dom.QName;
52
import org.exist.dom.memtree.*;
53
import org.exist.storage.DBBroker;
54
import org.exist.xquery.Expression;
55
import org.exist.xquery.NameTest;
56
import org.exist.xquery.XPathException;
57
import org.exist.xquery.value.*;
58
import org.w3c.dom.Node;
59
import org.xml.sax.ContentHandler;
60
import org.xml.sax.SAXException;
61
import org.xml.sax.helpers.AttributesImpl;
62

63
import javax.xml.stream.XMLInputFactory;
64
import javax.xml.stream.XMLStreamConstants;
65
import javax.xml.stream.XMLStreamException;
66
import javax.xml.stream.XMLStreamReader;
67
import javax.xml.transform.dom.DOMSource;
68
import javax.xml.xquery.XQException;
69
import javax.xml.xquery.XQItemType;
70
import java.io.Reader;
71
import java.io.StringReader;
72
import java.util.Properties;
73

74

75
/**
76
 * A utility class that provides marshalling services for external variables and methods 
77
 * to create DOM Nodes from streamed representation.
78
 * 
79
 * @author Wolfgang Meier
80
 *
81
 */
82
public class Marshaller {
×
83

84
    public final static String NAMESPACE = "http://exist-db.org/xquery/types/serialized";
85
    public final static String PREFIX = "sx";
86

87
    
88
    private final static Properties OUTPUT_PROPERTIES = new Properties();
1✔
89

90
    private final static String VALUE_ELEMENT = "value";
91
    private final static String VALUE_ELEMENT_QNAME = PREFIX + ":value";
92
    private final static QName VALUE_QNAME = new QName(VALUE_ELEMENT,  NAMESPACE, PREFIX);
1✔
93
    
94
    private final static String SEQ_ELEMENT = "sequence";
95
    private final static String SEQ_ELEMENT_QNAME = PREFIX + ":sequence";
96
    
97
    private final static String ATTR_TYPE = "type";
98
    private final static String ATTR_ITEM_TYPE = "item-type";
99

100
    public final static QName ROOT_ELEMENT_QNAME = new QName(SEQ_ELEMENT, NAMESPACE, PREFIX);
1✔
101
    
102
    /**
103
     * Marshall a sequence in an xml based string representation.
104
     *
105
     * @param broker the database broker
106
     * @param seq Sequence to be marshalled
107
     * @param handler Content handler for building the resulting string
108
     *
109
     * @throws XPathException if an XPath error occurs
110
     * @throws SAXException if a SAX parsing exception occurs
111
     */
112
    public static void marshall(final DBBroker broker, final Sequence seq, final ContentHandler handler)
113
            throws XPathException, SAXException {
114
        final AttributesImpl attrs = new AttributesImpl();
1✔
115
        attrs.addAttribute("", ATTR_ITEM_TYPE, ATTR_ITEM_TYPE, "CDATA", Type.getTypeName(seq.getItemType()));
1✔
116
        handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME, attrs);
1✔
117
        for (final SequenceIterator i = seq.iterate(); i.hasNext(); ) {
1✔
118
            marshallItem(broker, i.nextItem(), handler);
1✔
119
        }
120
        handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME);
1✔
121
    }
1✔
122
    
123
    
124
    /**
125
     * Marshall the items of a sequence in  an xml based string representation.
126
     *
127
     * @param broker the database broker
128
     * @param seq Sequence which items are to be marshalled
129
     * @param start index of first item to be marshalled
130
     * @param howmany number of items following and including the first to be marshalled
131
     * @param handler Content handler for building the resulting string
132
     *
133
     * @throws XPathException if an XPath error occurs
134
     * @throws SAXException if a SAX parsing exception occurs
135
     */
136
    public static void marshall(final DBBroker broker, final Sequence seq, final int start, final int howmany,
137
            final ContentHandler handler) throws XPathException, SAXException {
138
        final AttributesImpl attrs = new AttributesImpl();
×
139
        attrs.addAttribute("", ATTR_ITEM_TYPE, ATTR_ITEM_TYPE, "CDATA", Type.getTypeName(seq.getItemType()));
×
140
        handler.startElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME, attrs);
×
141
        for (int i = start; i < howmany && i < seq.getItemCount(); i++ ) {
×
142
                
143
            marshallItem(broker, seq.itemAt(i), handler);
×
144
        }
145
        handler.endElement(NAMESPACE, SEQ_ELEMENT, SEQ_ELEMENT_QNAME);
×
146
    }
×
147

148
    /**
149
     * Marshall an item in an xml based string representation.
150
     *
151
     * @param broker the database broker
152
     * @param item Sequence(or Item) to me marshalled
153
     * @param handler Content handler for building the resulting string
154
     *
155
     * @throws XPathException if an XPath error occurs
156
     * @throws SAXException if a SAX parsing exception occurs
157
     */
158
    public static void marshallItem(final DBBroker broker, final Item item, final ContentHandler handler)
159
            throws SAXException, XPathException {
160
        final AttributesImpl attrs = new AttributesImpl();
1✔
161
        int type = item.getType();
1✔
162
        if (type == Type.NODE) {
1✔
163
            final short nodeType = ((NodeValue)item).getNode().getNodeType();
1✔
164
            type = Type.fromDomNodeType(nodeType);
1✔
165
        }
166
        attrs.addAttribute("", ATTR_TYPE, ATTR_TYPE, "CDATA", Type.getTypeName(type));
1✔
167
        if (Type.subTypeOf(item.getType(), Type.NODE)) {
1✔
168
            handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME, attrs);
1✔
169
            final NodeValue nv = (NodeValue) item;
1✔
170
            nv.toSAX(broker, handler, OUTPUT_PROPERTIES);
1✔
171
            handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME);
1✔
172
        } else {
1✔
173
            handler.startElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME, attrs);
1✔
174
            final String value = item.getStringValue();
1✔
175
            handler.characters(value.toCharArray(), 0, value.length());
1✔
176
            handler.endElement(NAMESPACE, VALUE_ELEMENT, VALUE_ELEMENT_QNAME);
1✔
177
        }
178
    }
1✔
179

180
    public static Sequence demarshall(DBBroker broker, Reader reader) throws XMLStreamException, XPathException {
181
        final XMLInputFactory factory = XMLInputFactory.newInstance();
1✔
182
        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
1✔
183
        factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
1✔
184
        final XMLStreamReader parser = factory.createXMLStreamReader(reader);
1✔
185
        return demarshall(broker, parser);
1✔
186
    }
187
    
188
    public static Sequence demarshall(DBBroker broker,Node n) throws XMLStreamException, XPathException {
189
            final DOMSource source = new DOMSource(n, null);
×
190
            final XMLInputFactory factory = XMLInputFactory.newInstance();
×
191
        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
×
192
        factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
×
193
        
194
        final XMLStreamReader parser = factory.createXMLStreamReader(source);
×
195
            return demarshall(broker,parser);            
×
196
            
197
    }
198

199
    public static Sequence demarshall(DBBroker broker, XMLStreamReader parser) throws XMLStreamException, XPathException {
200
        int event = parser.next();
1✔
201
        while (event != XMLStreamConstants.START_ELEMENT)
1!
202
            event = parser.next();
×
203
        if (!NAMESPACE.equals(parser.getNamespaceURI()))
1!
204
            {throw new XMLStreamException("Root element is not in the correct namespace. Expected: " + NAMESPACE);}
×
205
        if (!SEQ_ELEMENT.equals(parser.getLocalName()))
1!
206
            {throw new XMLStreamException("Root element should be a " + SEQ_ELEMENT_QNAME);}
×
207
        final ValueSequence result = new ValueSequence();
1✔
208
        while ((event = parser.next()) != XMLStreamConstants.END_DOCUMENT) {
1!
209
            switch (event) {
1!
210
                case XMLStreamConstants.START_ELEMENT :
211
                    if (NAMESPACE.equals(parser.getNamespaceURI()) && VALUE_ELEMENT.equals(parser.getLocalName())) {
1!
212
                        String typeName = null;
1✔
213
                        // scan through attributes instead of direct lookup to work around issue in xerces
214
                        for (int i = 0; i < parser.getAttributeCount(); i++) {
1!
215
                            if (ATTR_TYPE.equals(parser.getAttributeLocalName(i))) {
1!
216
                                typeName = parser.getAttributeValue(i);
1✔
217
                                break;
1✔
218
                            }
219
                        }
220
                        if (typeName != null) {
1!
221
                            final int type = Type.getType(typeName);
1✔
222
                            Item item;
223
                            if (Type.subTypeOf(type, Type.NODE))
1✔
224
                                {item = streamToDOM(type, parser, null);}
1✔
225
                            else
226
                                {item = new StringValue(null, parser.getElementText()).convertTo(type);}
1✔
227
                            result.add(item);
1✔
228
                        }
229
                    }
230
                    break;
1✔
231
                case XMLStreamConstants.END_ELEMENT :
232
                    if (NAMESPACE.equals(parser.getNamespaceURI()) && SEQ_ELEMENT.equals(parser.getLocalName()))
1!
233
                        {return result;}
1✔
234
                    break;
235
            }
236
        }
237
        return result;
×
238
    }
239

240
    public static Sequence demarshall(NodeImpl node) throws XMLStreamException, XPathException {
241
        final String ns = node.getNamespaceURI();
×
242
        if (ns == null || !NAMESPACE.equals(ns)) {
×
243
            throw new XMLStreamException("Root element is not in the correct namespace. Expected: " + NAMESPACE);
×
244
        }
245
        if (!SEQ_ELEMENT.equals(node.getLocalName()))
×
246
            {throw new XMLStreamException("Root element should be a " + SEQ_ELEMENT_QNAME);}
×
247
        final ValueSequence result = new ValueSequence();
×
248
        final InMemoryNodeSet values = new InMemoryNodeSet();
×
249
        node.selectChildren(new NameTest(Type.ELEMENT, VALUE_QNAME), values);
×
250
        for (final SequenceIterator i = values.iterate(); i.hasNext();) {
×
251
            final ElementImpl child = (ElementImpl) i.nextItem();
×
252
            final String typeName = child.getAttribute(ATTR_TYPE);
×
253
            if (!typeName.isEmpty()) {
×
254
                final int type = Type.getType(typeName);
×
255
                Item item;
256
                if (Type.subTypeOf(type, Type.NODE)) {
×
257
                    item = (Item) child.getFirstChild();
×
258
                    if (type == Type.DOCUMENT) {
×
259
                        final DocumentImpl n = (DocumentImpl) item;
×
260
                        final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(n.getExpression());
×
261
                        try {
262
                            receiver.startDocument();
×
263
                            n.copyTo(n, receiver);
×
264
                            receiver.endDocument();
×
265
                        } catch (final SAXException e) {
×
266
                            throw new XPathException(item != null ? item.getExpression() : null, "Error while demarshalling node: " + e.getMessage(), e);
×
267
                        }
268
                        item = (Item) receiver.getDocument();
×
269
                    }
270
                } else {
×
271
                    final StringBuilder data = new StringBuilder();
×
272
                    Node txt = child.getFirstChild();
×
273
                    while (txt != null) {
×
274
                        if (!(txt.getNodeType() == Node.TEXT_NODE || txt.getNodeType() == Node.CDATA_SECTION_NODE)) {
×
275
                            throw new XMLStreamException("sx:value should only contain text if type is " + typeName);
×
276
                        }
277
                        data.append(txt.getNodeValue());
×
278
                        txt = txt.getNextSibling();
×
279
                    }
280
                    item = new StringValue(data.toString()).convertTo(type);
×
281
                }
282
                result.add(item);
×
283
            }
284
        }
285
        return result;
×
286
    }
287

288
    public static Item streamToDOM(XMLStreamReader parser, XQItemType type) throws XMLStreamException, XQException {
289
        if (type.getBaseType() == XQItemType.XQITEMKIND_DOCUMENT_ELEMENT ||
×
290
                type.getBaseType() == XQItemType.XQITEMKIND_DOCUMENT_SCHEMA_ELEMENT)
×
291
            {return streamToDOM(Type.DOCUMENT, parser, null);}
×
292
        else
293
            {return streamToDOM(Type.ELEMENT, parser, null);}
×
294
    }
295

296
    /**
297
     * Creates an Item from a streamed representation.
298
     *
299
     * @param rootType the type of the root node
300
     * @param parser Parser to read xml elements from
301
     * @return item the item
302
     *
303
     * @throws XMLStreamException if an error occurs during streaming.
304
     */
305
    public static Item streamToDOM(int rootType, XMLStreamReader parser) throws XMLStreamException {
306
        return streamToDOM(rootType, parser, null);
×
307
    }
308

309
    /**
310
     * Creates an Item from a streamed representation.
311
     *
312
     * @param rootType the type of the root node
313
     * @param parser Parser to read xml elements from
314
     * @param expression the expression from which the item derives
315
     * @return item the item
316
     *
317
     * @throws XMLStreamException if an error occurs during streaming.
318
     */
319
    public static Item streamToDOM(int rootType, XMLStreamReader parser, final Expression expression) throws XMLStreamException {
320
        final MemTreeBuilder builder = new MemTreeBuilder(expression);
1✔
321
        builder.startDocument();
1✔
322
        int event;
323
        boolean finish = false;
1✔
324
        while ((event = parser.next()) != XMLStreamConstants.END_DOCUMENT) {
1!
325
            switch (event) {
1!
326
                case XMLStreamConstants.START_ELEMENT :
327
                    final AttributesImpl attribs = new AttributesImpl();
×
328
                    for (int i = 0; i < parser.getAttributeCount(); i++) {
×
329
                        final javax.xml.namespace.QName qn = parser.getAttributeName(i);
×
330
                        attribs.addAttribute(qn.getNamespaceURI(), qn.getLocalPart(), qn.getPrefix() + ':' + qn.getLocalPart(),
×
331
                                parser.getAttributeType(i), parser.getAttributeValue(i));
×
332
                    }
333
                   builder.startElement(QName.fromJavaQName(parser.getName()), attribs);
×
334
//                    for (int i = 0; i < parser.getNamespaceCount(); i++) {
335
//                        builder.namespaceNode(parser.getNamespacePrefix(i), parser.getNamespaceURI(i));
336
//                    }
337
                    break;
×
338
                case XMLStreamConstants.END_ELEMENT :
339
                    if (NAMESPACE.equals(parser.getNamespaceURI()) && VALUE_ELEMENT.equals(parser.getLocalName()))
1!
340
                        {finish = true;}
1✔
341
                    else
342
                        {builder.endElement();}
×
343
                    break;
×
344
                case XMLStreamConstants.CHARACTERS :
345
                    builder.characters(parser.getText());
1✔
346
                    break;
347
            }
348
            if (finish) {break;}
1✔
349
        }
350
        builder.endDocument();
1✔
351
        if (rootType == Type.DOCUMENT)
1!
352
            {return builder.getDocument();}
×
353
        else if (rootType == Type.ELEMENT)
1!
354
            {return (NodeImpl) builder.getDocument().getDocumentElement();}
×
355
        else
356
            {return (NodeImpl) builder.getDocument().getFirstChild();}
1✔
357
    }
358
    
359
    
360
    
361
    /**
362
     * Creates an Item from a streamed representation.
363
     *
364
     * @param reader the reader.
365
     * @param type the type of the item.
366
     * @return item the result item
367
     *
368
     * @throws XMLStreamException if an error occurs during streaming.
369
     * @throws XQException if any other error occurs.
370
     */
371
    public static Item streamToDOM(Reader reader, XQItemType type) throws XMLStreamException, XQException {
372
            final XMLInputFactory factory = XMLInputFactory.newInstance();
×
373
        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
×
374
        factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
×
375
        final XMLStreamReader parser = factory.createXMLStreamReader(reader);
×
376
        
377
        return streamToDOM(parser, type);
×
378
    }
379
    
380
    
381
    
382
    /**
383
     * Creates a node from a string representation.
384
     *
385
     * @param content the content
386
     * @return node the result node.
387
     *
388
     * @throws XMLStreamException if an error occurs during streaming.
389
     */
390
    public static Node streamToNode(String content) throws XMLStreamException {
391
            final StringReader reader = new StringReader(content);
1✔
392
            return streamToNode(reader, null);
1✔
393
    }
394
    
395
    
396
   
397
    /**
398
     * Creates a node from a streamed representation.
399
     *
400
     * @param reader the reader.
401
     * @return item the result item.
402
     *
403
     * @throws XMLStreamException if an error occurs during streaming.
404
     */
405
    public static Node streamToNode(Reader reader) throws XMLStreamException {
406
        return streamToNode(reader, null);
×
407
    }
408
   
409
    /**
410
     * Creates a node from a streamed representation.
411
     *
412
     * @param reader the reader.
413
     * @param expression the expression from which te node derives
414
     * @return item the result item.
415
     *
416
     * @throws XMLStreamException if an error occurs during streaming.
417
     */
418
    public static Node streamToNode(Reader reader, final Expression expression) throws XMLStreamException {
419
            final XMLInputFactory factory = XMLInputFactory.newInstance();
1✔
420
        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
1✔
421
        factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
1✔
422
        final XMLStreamReader parser = factory.createXMLStreamReader(reader);
1✔
423
        final MemTreeBuilder builder = new MemTreeBuilder(expression);
1✔
424
        builder.startDocument();
1✔
425
        int event;
426
        boolean finish = false;
1✔
427
        while ((event = parser.next()) != XMLStreamConstants.END_DOCUMENT) {
1✔
428
            switch (event) {
1!
429
                case XMLStreamConstants.START_ELEMENT :
430
                    final AttributesImpl attribs = new AttributesImpl();
1✔
431
                    for (int i = 0; i < parser.getAttributeCount(); i++) {
1✔
432
                        final javax.xml.namespace.QName qn = parser.getAttributeName(i);
1✔
433
                        attribs.addAttribute(qn.getNamespaceURI(), qn.getLocalPart(), qn.getPrefix() + ':' + qn.getLocalPart(),
1✔
434
                                parser.getAttributeType(i), parser.getAttributeValue(i));
1✔
435
                    }
436
                   builder.startElement(QName.fromJavaQName(parser.getName()), attribs);
1✔
437
//                    for (int i = 0; i < parser.getNamespaceCount(); i++) {
438
//                        builder.namespaceNode(parser.getNamespacePrefix(i), parser.getNamespaceURI(i));
439
//                    }
440
                    break;
1✔
441
                case XMLStreamConstants.END_ELEMENT :
442
                    if (NAMESPACE.equals(parser.getNamespaceURI()) && VALUE_ELEMENT.equals(parser.getLocalName()))
1!
443
                        {finish = true;}
×
444
                    else
445
                        {builder.endElement();}
1✔
446
                    break;
1✔
447
                case XMLStreamConstants.CHARACTERS :
448
                    builder.characters(parser.getText());
1✔
449
                    break;
450
            }
451
            if (finish) {break;}
1!
452
        }
453
        builder.endDocument();
1✔
454
        return builder.getDocument().getDocumentElement();
1✔
455
    }
456
}
457

458

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc