• 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

75.4
/exist-core/src/main/java/org/exist/util/serializer/DOMStreamer.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.util.serializer;
50

51
import java.util.*;
52

53
import org.apache.logging.log4j.Logger;
54
import org.apache.logging.log4j.LogManager;
55
import org.exist.dom.QName;
56
import org.exist.dom.QName.IllegalQNameException;
57
import org.w3c.dom.Attr;
58
import org.w3c.dom.CharacterData;
59
import org.w3c.dom.Comment;
60
import org.w3c.dom.NamedNodeMap;
61
import org.w3c.dom.Node;
62
import org.w3c.dom.ProcessingInstruction;
63
import org.xml.sax.ContentHandler;
64
import org.xml.sax.SAXException;
65
import org.xml.sax.ext.LexicalHandler;
66
import org.xml.sax.helpers.AttributesImpl;
67
import org.xml.sax.helpers.NamespaceSupport;
68

69
import javax.annotation.Nullable;
70
import javax.xml.XMLConstants;
71

72
/**
73
 * General purpose class to stream a DOM node to SAX.
74
 *
75
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
76
 */
77
public class DOMStreamer {
78

79
    private static final Logger LOG = LogManager.getLogger(DOMStreamer.class);
1✔
80

81
    private ContentHandler contentHandler = null;
1✔
82
    private LexicalHandler lexicalHandler = null;
1✔
83
    private final NamespaceSupport nsSupport = new NamespaceSupport();
1✔
84
    private final Map<String, String> namespaceDecls = new HashMap<>();
1✔
85
    private final Deque<ElementInfo> stack = new ArrayDeque<>();
1✔
86

87
    public DOMStreamer() {
1✔
88
    }
1✔
89

90
    public DOMStreamer(final ContentHandler contentHandler, final LexicalHandler lexicalHandler) {
×
91
        this.contentHandler = contentHandler;
×
92
        this.lexicalHandler = lexicalHandler;
×
93
    }
×
94

95
    public void setContentHandler(final ContentHandler handler) {
96
        contentHandler = handler;
1✔
97
    }
1✔
98

99
    public void setLexicalHandler(final LexicalHandler handler) {
100
        lexicalHandler = handler;
1✔
101
    }
1✔
102

103
    /**
104
     * Reset internal state for reuse. Registered handlers will be set
105
     * to null.
106
     */
107
    public void reset() {
108
        nsSupport.reset();
1✔
109
        namespaceDecls.clear();
1✔
110
        stack.clear();
1✔
111
        contentHandler = null;
1✔
112
        lexicalHandler = null;
1✔
113
    }
1✔
114

115
    /**
116
     * Serialize the given node and all its descendants to SAX.
117
     *
118
     * @param node the node to serialize
119
     * @throws SAXException if an error occurs during serialization.
120
     */
121
    public void serialize(final Node node) throws SAXException {
122
        serialize(node, false);
×
123
    }
×
124

125
    /**
126
     * Serialize the given node and all its descendants to SAX. If
127
     * callDocumentEvents is set to false, startDocument/endDocument
128
     * events will not be fired.
129
     *
130
     * @param node the node to serialize
131
     * @param callDocumentEvents whether we shoiuld call the document events startDocument/endDocument
132
     * @throws SAXException if an error occurs during serialization.
133
     */
134
    public void serialize(Node node, final boolean callDocumentEvents) throws SAXException {
135
        if (callDocumentEvents) {
1✔
136
            contentHandler.startDocument();
1✔
137
        }
138
        final Node top = node;
1✔
139
        while (node != null) {
1✔
140
            startNode(node);
1✔
141
            Node nextNode = node.getFirstChild();
1✔
142
            while (nextNode == null) {
1✔
143
                endNode(node);
1✔
144
                if (top != null && top.equals(node)) {
1!
145
                    break;
×
146
                }
147
                nextNode = node.getNextSibling();
1✔
148
                if (nextNode == null) {
1✔
149
                    node = node.getParentNode();
1✔
150
                    if (node == null || (top != null && top.equals(node))) {
1!
151
                        endNode(node);
1✔
152
                        //nextNode = null;
153
                        break;
1✔
154
                    }
155
                }
156
            }
157
            node = nextNode;
1✔
158
        }
159
        if (callDocumentEvents) {
1✔
160
            contentHandler.endDocument();
1✔
161
        }
162
    }
1✔
163

164
    protected void startNode(final Node node) throws SAXException {
165
        String cdata;
166
        switch (node.getNodeType()) {
1!
167
            case Node.DOCUMENT_NODE:
168
            case Node.DOCUMENT_FRAGMENT_NODE:
169
                break;
1✔
170
            case Node.ELEMENT_NODE:
171
                namespaceDecls.clear();
1✔
172
                nsSupport.pushContext();
1✔
173
                @Nullable String uri = node.getNamespaceURI();
1✔
174
                @Nullable String prefix = node.getPrefix();
1✔
175
                if (uri == null) {
1✔
176
                    uri = XMLConstants.NULL_NS_URI;
1✔
177
                }
178
                if (prefix == null) {
1!
179
                    prefix = XMLConstants.DEFAULT_NS_PREFIX;
1✔
180
                }
181
                if ((!(XMLConstants.NULL_NS_URI.equals(uri) && XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))) && nsSupport.getURI(prefix) == null) {
1!
182
                    namespaceDecls.put(prefix, uri);
1✔
183
                    nsSupport.declarePrefix(prefix, uri);
1✔
184
                }
185
                // check attributes for required namespace declarations
186
                final NamedNodeMap attrs = node.getAttributes();
1✔
187
                Attr nextAttr;
188
                String attrName;
189
                for (int i = 0; i < attrs.getLength(); i++) {
1✔
190
                    nextAttr = (Attr) attrs.item(i);
1✔
191
                    attrName = nextAttr.getName();
1✔
192
                    if (XMLConstants.XMLNS_ATTRIBUTE.equals(attrName)) {
1✔
193
                        if (nsSupport.getURI(XMLConstants.NULL_NS_URI) == null) {
1!
194
                            uri = nextAttr.getValue();
×
195
                            namespaceDecls.put(XMLConstants.DEFAULT_NS_PREFIX, uri);
×
196
                            nsSupport.declarePrefix(XMLConstants.DEFAULT_NS_PREFIX, uri);
×
197
                        }
198
                    } else if (attrName.startsWith(XMLConstants.XMLNS_ATTRIBUTE + ":")) {
1!
199
                        prefix = attrName.substring(6);
×
200
                        if (nsSupport.getURI(prefix) == null) {
×
201
                            uri = nextAttr.getValue();
×
202
                            namespaceDecls.put(prefix, uri);
×
203
                            nsSupport.declarePrefix(prefix, uri);
×
204
                        }
205
                    } else if (attrName.indexOf(':') > 0) {
1!
206
                        prefix = nextAttr.getPrefix();
×
207
                        if (prefix == null) {
×
208
                            prefix = XMLConstants.DEFAULT_NS_PREFIX;
×
209
                        }
210
                        uri = nextAttr.getNamespaceURI();
×
211
                        if (nsSupport.getURI(prefix) == null) {
×
212
                            namespaceDecls.put(prefix, uri);
×
213
                            nsSupport.declarePrefix(prefix, uri);
×
214
                        }
215
                    }
216
                }
217
                final ElementInfo info = new ElementInfo(node);
1✔
218
                String[] declaredPrefixes = null;
1✔
219
                if (!namespaceDecls.isEmpty()) {
1✔
220
                    declaredPrefixes = new String[namespaceDecls.size()];
1✔
221
                }
222
                // output all namespace declarations
223
                Map.Entry<String, String> nsEntry;
224
                int j = 0;
1✔
225
                for (final Iterator<Map.Entry<String, String>> i = namespaceDecls.entrySet().iterator(); i.hasNext(); j++) {
1✔
226
                    nsEntry = i.next();
1✔
227
                    declaredPrefixes[j] = nsEntry.getKey();
1✔
228
                    contentHandler.startPrefixMapping(declaredPrefixes[j], nsEntry.getValue());
1✔
229
                }
230
                info.prefixes = declaredPrefixes;
1✔
231
                stack.push(info);
1✔
232
                // output attributes
233
                final AttributesImpl saxAttrs = new AttributesImpl();
1✔
234
                String attrNS;
235
                String attrLocalName;
236
                for (int i = 0; i < attrs.getLength(); i++) {
1✔
237
                    nextAttr = (Attr) attrs.item(i);
1✔
238
                    attrNS = nextAttr.getNamespaceURI();
1✔
239
                    if (attrNS == null) {
1✔
240
                        attrNS = XMLConstants.NULL_NS_URI;
1✔
241
                    }
242
                    attrLocalName = nextAttr.getLocalName();
1✔
243
                    if (attrLocalName == null) {
1✔
244
                        try {
245
                            attrLocalName = QName.extractLocalName(nextAttr.getNodeName());
1✔
246
                        } catch (final IllegalQNameException e) {
1✔
247
                            throw new SAXException(e);
×
248
                        }
249
                    }
250
                    saxAttrs.addAttribute(
1✔
251
                            attrNS,
1✔
252
                            attrLocalName,
1✔
253
                            nextAttr.getNodeName(),
1✔
254
                            "CDATA",
1✔
255
                            nextAttr.getValue()
1✔
256
                    );
257
                }
258
                String localName = node.getLocalName();
1✔
259
                if (localName == null) {
1✔
260
                    try {
261
                        localName = QName.extractLocalName(node.getNodeName());
1✔
262
                    } catch (final IllegalQNameException e) {
1✔
263
                        throw new SAXException(e);
×
264
                    }
265
                }
266
                String namespaceURI = node.getNamespaceURI();
1✔
267
                if (namespaceURI == null) {
1✔
268
                    namespaceURI = XMLConstants.NULL_NS_URI;
1✔
269
                }
270
                contentHandler.startElement(namespaceURI, localName,
1✔
271
                        node.getNodeName(), saxAttrs);
1✔
272
                break;
1✔
273
            case Node.TEXT_NODE:
274
                cdata = ((CharacterData) node).getData();
1✔
275
                contentHandler.characters(cdata.toCharArray(), 0, cdata.length());
1✔
276
                break;
1✔
277
            case Node.CDATA_SECTION_NODE:
278
                cdata = ((CharacterData) node).getData();
×
279
                if (lexicalHandler != null) {
×
280
                    lexicalHandler.startCDATA();
×
281
                }
282
                contentHandler.characters(cdata.toCharArray(), 0, cdata.length());
×
283
                if (lexicalHandler != null) {
×
284
                    lexicalHandler.endCDATA();
×
285
                }
286
                break;
×
287
            case Node.ATTRIBUTE_NODE:
288
                break;
×
289
            case Node.PROCESSING_INSTRUCTION_NODE:
290
                contentHandler.processingInstruction(
×
291
                        ((ProcessingInstruction) node).getTarget(),
×
292
                        ((ProcessingInstruction) node).getData());
×
293
                break;
×
294
            case Node.COMMENT_NODE:
295
                if (lexicalHandler != null) {
1!
296
                    cdata = ((Comment) node).getData();
1✔
297
                    lexicalHandler.comment(cdata.toCharArray(), 0, cdata.length());
1✔
298
                }
299
                break;
1✔
300
            default:
301
                //TODO : what kind of default here ? -pb
302
                LOG.error("Unknown node type: {}", node.getNodeType());
×
303
                break;
304
        }
305
    }
1✔
306

307
    protected void endNode(final Node node) throws SAXException {
308
        if (node == null) {
1✔
309
            return;
1✔
310
        }
311
        if (node.getNodeType() == Node.ELEMENT_NODE) {
1✔
312
            final ElementInfo info = stack.pop();
1✔
313
            nsSupport.popContext();
1✔
314
            String localName = node.getLocalName();
1✔
315
            if (localName == null) {
1✔
316
                try {
317
                    localName = QName.extractLocalName(node.getNodeName());
1✔
318
                } catch (final IllegalQNameException e) {
1✔
319
                    throw new SAXException(e);
×
320
                }
321
            }
322
            String namespaceURI = node.getNamespaceURI();
1✔
323
            if (namespaceURI == null) {
1✔
324
                namespaceURI = XMLConstants.NULL_NS_URI;
1✔
325
            }
326
            contentHandler.endElement(namespaceURI, localName, node.getNodeName());
1✔
327
            if (info.prefixes != null) {
1✔
328
                for (int i = 0; i < info.prefixes.length; i++) {
1✔
329
                    contentHandler.endPrefixMapping(info.prefixes[i]);
1✔
330
                }
331
            }
332
        }
333
    }
1✔
334

335
    private static class ElementInfo {
336
        final Node element;
337
        String[] prefixes = null;
1✔
338

339
        public ElementInfo(final Node element) {
1✔
340
            this.element = element;
1✔
341
        }
1✔
342
    }
343
}
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