• 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

0.0
/exist-core/src/main/java/org/exist/dom/memtree/DOMIndexer.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.dom.memtree;
50

51
import org.apache.logging.log4j.LogManager;
52
import org.apache.logging.log4j.Logger;
53
import org.exist.EXistException;
54
import org.exist.Namespaces;
55
import org.exist.collections.CollectionConfiguration;
56
import org.exist.dom.QName;
57
import org.exist.dom.persistent.AttrImpl;
58
import org.exist.dom.persistent.CommentImpl;
59
import org.exist.dom.persistent.ElementImpl;
60
import org.exist.dom.persistent.ProcessingInstructionImpl;
61
import org.exist.dom.persistent.TextImpl;
62
import org.exist.dom.persistent.*;
63
import org.exist.numbering.NodeId;
64
import org.exist.storage.DBBroker;
65
import org.exist.storage.IndexSpec;
66
import org.exist.storage.NodePath;
67
import org.exist.storage.txn.Txn;
68
import org.exist.util.pool.NodePool;
69
import org.exist.xquery.Expression;
70
import org.w3c.dom.DOMException;
71
import org.w3c.dom.Node;
72

73
import javax.xml.XMLConstants;
74
import java.util.ArrayDeque;
75
import java.util.Deque;
76
import java.util.HashMap;
77
import java.util.Map;
78

79
/**
80
 * Helper class to make a in-memory document fragment persistent. The class
81
 * directly accesses the in-memory document structure and writes it into a
82
 * temporary doc on the database. This is much faster than first serializing
83
 * the document tree to SAX and passing it to {@link org.exist.collections.Collection#store(org.exist.storage.txn.Txn, org.exist.storage.DBBroker, org.exist.collections.IndexInfo, org.xml.sax.InputSource)}.
84
 *
85
 * As the in-memory document fragment may not be a well-formed XML doc (having more than one root element), a wrapper element is put around the
86
 * content nodes.
87
 *
88
 * @author wolf
89
 */
90
public class DOMIndexer {
91

92
    private static final Logger LOG = LogManager.getLogger(DOMIndexer.class);
×
93
    private static final QName ROOT_QNAME = new QName("temp", Namespaces.EXIST_NS, Namespaces.EXIST_NS_PREFIX);
×
94

95
    private final DBBroker broker;
96
    private final Txn transaction;
97
    private final DocumentImpl doc;
98
    private final org.exist.dom.persistent.DocumentImpl targetDoc;
99
    private final IndexSpec indexSpec;
100

101
    private final Deque<ElementImpl> stack = new ArrayDeque<>();
×
102
    private StoredNode prevNode = null;
×
103

104
    private final TextImpl text = new TextImpl((Expression) null);
×
105
    private final CommentImpl comment = new CommentImpl((Expression) null);
×
106
    private final ProcessingInstructionImpl pi = new ProcessingInstructionImpl(null);
×
107

108
    public DOMIndexer(final DBBroker broker, final Txn transaction, final DocumentImpl doc,
×
109
                      final org.exist.dom.persistent.DocumentImpl targetDoc) {
110
        this.broker = broker;
×
111
        this.transaction = transaction;
×
112
        this.doc = doc;
×
113
        this.targetDoc = targetDoc;
×
114
        final CollectionConfiguration config = targetDoc.getCollection().getConfiguration(broker);
×
115
        if(config != null) {
×
116
            this.indexSpec = config.getIndexConfiguration();
×
117
        } else {
×
118
            this.indexSpec = null;
×
119
        }
120
    }
×
121

122
    /**
123
     * Scan the DOM tree once to determine its structure.
124
     *
125
     * @throws EXistException DOCUMENT ME
126
     */
127
    public void scan() throws EXistException {
128
        //Creates a dummy DOCTYPE
129
        final org.exist.dom.persistent.DocumentTypeImpl dt = new org.exist.dom.persistent.DocumentTypeImpl((doc != null) ? doc.getExpression() : null, "temp", null, "");
×
130
        targetDoc.setDocumentType(dt);
×
131
    }
×
132

133
    /**
134
     * Store the nodes.
135
     */
136
    public void store() {
137
        //Create a wrapper element as root node
138
        final ElementImpl elem = new ElementImpl(null, ROOT_QNAME, broker.getBrokerPool().getSymbols());
×
139
        elem.setNodeId(broker.getBrokerPool().getNodeFactory().createInstance());
×
140
        elem.setOwnerDocument(targetDoc);
×
141
        elem.setChildCount(doc.getChildCount());
×
142
        elem.addNamespaceMapping(Namespaces.EXIST_NS_PREFIX, Namespaces.EXIST_NS);
×
143
        final NodePath path = new NodePath();
×
144
        path.addComponent(ROOT_QNAME);
×
145
        stack.push(elem);
×
146
        broker.storeNode(transaction, elem, path, indexSpec);
×
147
        targetDoc.appendChild((NodeHandle) elem);
×
148
        elem.setChildCount(0);
×
149
        // store the document nodes
150
        int top = (doc.size > 1) ? 1 : -1;
×
151
        while(top > 0) {
×
152
            store(top, path);
×
153
            top = doc.getNextSiblingFor(top);
×
154
        }
155
        //Close the wrapper element
156
        stack.pop();
×
157
        broker.endElement(elem, path, null);
×
158
        path.removeLastComponent();
×
159
    }
×
160

161
    private void store(final int top, final NodePath currentPath) {
162
        int nodeNr = top;
×
163

164
        while(nodeNr > 0) {
×
165
            startNode(nodeNr, currentPath);
×
166
            int nextNode = doc.getFirstChildFor(nodeNr);
×
167

168
            while(nextNode == -1) {
×
169
                endNode(nodeNr, currentPath);
×
170

171
                if(top == nodeNr) {
×
172
                    break;
×
173
                }
174
                nextNode = doc.getNextSiblingFor(nodeNr);
×
175

176
                if(nextNode == -1) {
×
177
                    nodeNr = doc.getParentNodeFor(nodeNr);
×
178

179
                    if((nodeNr == -1) || (top == nodeNr)) {
×
180
                        endNode(nodeNr, currentPath);
×
181
                        nextNode = -1;
×
182
                        break;
×
183
                    }
184
                }
185
            }
186
            nodeNr = nextNode;
×
187
        }
188
    }
×
189

190
    /**
191
     * DOCUMENT ME!
192
     *
193
     * @param nodeNr
194
     * @param currentPath DOCUMENT ME!
195
     */
196
    private void startNode(final int nodeNr, final NodePath currentPath) {
197
        switch(doc.nodeKind[nodeNr]) {
×
198

199
            case Node.ELEMENT_NODE: {
200
                final ElementImpl elem = (ElementImpl) NodePool.getInstance().borrowNode(Node.ELEMENT_NODE);
×
201
                if(stack.isEmpty()) {
×
202
                    elem.setNodeId(broker.getBrokerPool().getNodeFactory().createInstance());
×
203
                    initElement(nodeNr, elem);
×
204
                    stack.push(elem);
×
205
                    broker.storeNode(transaction, elem, currentPath, indexSpec);
×
206
                    targetDoc.appendChild((NodeHandle) elem);
×
207
                    elem.setChildCount(0);
×
208
                } else {
×
209
                    final ElementImpl last = stack.peek();
×
210
                    initElement(nodeNr, elem);
×
211
                    last.appendChildInternal(prevNode, elem);
×
212
                    stack.push(elem);
×
213
                    broker.storeNode(transaction, elem, currentPath, indexSpec);
×
214
                    elem.setChildCount(0);
×
215
                }
216
                setPrevious(null);
×
217
                currentPath.addComponent(elem.getQName());
×
218
                storeAttributes(nodeNr, elem, currentPath);
×
219
                break;
×
220
            }
221

222
            case Node.TEXT_NODE: {
223
                if((prevNode != null) && ((prevNode.getNodeType() == Node.TEXT_NODE) || (prevNode.getNodeType() == Node.CDATA_SECTION_NODE))) {
×
224
                    break;
×
225
                }
226
                final ElementImpl last = stack.peek();
×
227
                text.setData(new String(doc.characters, doc.alpha[nodeNr], doc.alphaLen[nodeNr]));
×
228
                text.setOwnerDocument(targetDoc);
×
229
                last.appendChildInternal(prevNode, text);
×
230
                setPrevious(text);
×
231
                broker.storeNode(transaction, text, null, indexSpec);
×
232
                break;
×
233
            }
234

235
            case Node.CDATA_SECTION_NODE: {
236
                final ElementImpl last = stack.peek();
×
237
                final org.exist.dom.persistent.CDATASectionImpl cdata = (org.exist.dom.persistent.CDATASectionImpl) NodePool.getInstance().borrowNode(Node.CDATA_SECTION_NODE);
×
238
                cdata.setData(doc.characters, doc.alpha[nodeNr], doc.alphaLen[nodeNr]);
×
239
                cdata.setOwnerDocument(targetDoc);
×
240
                last.appendChildInternal(prevNode, cdata);
×
241
                setPrevious(cdata);
×
242
                broker.storeNode(transaction, cdata, null, indexSpec);
×
243
                break;
×
244
            }
245

246
            case Node.COMMENT_NODE: {
247
                comment.setData(doc.characters, doc.alpha[nodeNr], doc.alphaLen[nodeNr]);
×
248
                comment.setOwnerDocument(targetDoc);
×
249
                if(stack.isEmpty()) {
×
250
                    comment.setNodeId(NodeId.DOCUMENT_NODE);
×
251
                    targetDoc.appendChild((NodeHandle) comment);
×
252
                    broker.storeNode(transaction, comment, null, indexSpec);
×
253
                } else {
×
254
                    final ElementImpl last = stack.peek();
×
255
                    last.appendChildInternal(prevNode, comment);
×
256
                    broker.storeNode(transaction, comment, null, indexSpec);
×
257
                    setPrevious(comment);
×
258
                }
259
                break;
×
260
            }
261

262
            case Node.PROCESSING_INSTRUCTION_NODE: {
263
                final QName qn = doc.nodeName[nodeNr];
×
264
                pi.setTarget(qn.getLocalPart());
×
265
                pi.setData(new String(doc.characters, doc.alpha[nodeNr], doc.alphaLen[nodeNr]));
×
266
                pi.setOwnerDocument(targetDoc);
×
267
                if(stack.isEmpty()) {
×
268
                    pi.setNodeId(NodeId.DOCUMENT_NODE);
×
269
                    targetDoc.appendChild((NodeHandle) pi);
×
270
                } else {
×
271
                    final ElementImpl last = stack.peek();
×
272
                    last.appendChildInternal(prevNode, pi);
×
273
                    setPrevious(pi);
×
274
                }
275
                broker.storeNode(transaction, pi, null, indexSpec);
×
276
                break;
×
277
            }
278

279
            default: {
280
                LOG.debug("Skipped indexing of in-memory node of type {}", doc.nodeKind[nodeNr]);
×
281
            }
282
        }
283
    }
×
284

285
    /**
286
     * DOCUMENT ME!
287
     *
288
     * @param nodeNr
289
     * @param elem
290
     */
291
    private void initElement(final int nodeNr, final ElementImpl elem) {
292
        final short attribs = (short) doc.getAttributesCountFor(nodeNr);
×
293
        elem.setOwnerDocument(targetDoc);
×
294
        elem.setAttributes(attribs);
×
295
        elem.setChildCount(doc.getChildCountFor(nodeNr) + attribs);
×
296
        elem.setNodeName(doc.nodeName[nodeNr], broker.getBrokerPool().getSymbols());
×
297
        final Map<String, String> ns = getNamespaces(nodeNr);
×
298
        if(ns != null) {
×
299
            elem.setNamespaceMappings(ns);
×
300
        }
301
    }
×
302

303
    private Map<String, String> getNamespaces(final int nodeNr) {
304
        int ns = doc.alphaLen[nodeNr];
×
305

306
        if(ns < 0) {
×
307
            return null;
×
308
        }
309

310
        final Map<String, String> map = new HashMap<>();
×
311

312
        while((ns < doc.nextNamespace) && (doc.namespaceParent[ns] == nodeNr)) {
×
313
            final QName qn = doc.namespaceCode[ns];
×
314

315
            if(XMLConstants.XMLNS_ATTRIBUTE.equals(qn.getLocalPart())) {
×
316
                map.put(XMLConstants.DEFAULT_NS_PREFIX, qn.getNamespaceURI());
×
317
            } else {
×
318
                map.put(qn.getLocalPart(), qn.getNamespaceURI());
×
319
            }
320
            ++ns;
×
321
        }
322

323
        return map;
×
324
    }
325

326
    /**
327
     * DOCUMENT ME!
328
     *
329
     * @param nodeNr
330
     * @param elem
331
     * @param path   DOCUMENT ME!
332
     * @throws DOMException
333
     */
334
    private void storeAttributes(final int nodeNr, final ElementImpl elem, final NodePath path) throws DOMException {
335
        int attr = doc.alpha[nodeNr];
×
336
        if(attr > -1) {
×
337
            while((attr < doc.nextAttr) && (doc.attrParent[attr] == nodeNr)) {
×
338
                final QName qn = doc.attrName[attr];
×
339
                final AttrImpl attrib = (AttrImpl) NodePool.getInstance().borrowNode(Node.ATTRIBUTE_NODE);
×
340
                attrib.setNodeName(qn, broker.getBrokerPool().getSymbols());
×
341
                attrib.setValue(doc.attrValue[attr]);
×
342
                attrib.setOwnerDocument(targetDoc);
×
343
                elem.appendChildInternal(prevNode, attrib);
×
344
                setPrevious(attrib);
×
345
                broker.storeNode(transaction, attrib, path, indexSpec);
×
346
                ++attr;
×
347
            }
348
        }
349
    }
×
350

351
    /**
352
     * DOCUMENT ME!
353
     *
354
     * @param nodeNr
355
     * @param currentPath DOCUMENT ME!
356
     */
357
    private void endNode(final int nodeNr, final NodePath currentPath) {
358
        if(doc.nodeKind[nodeNr] == Node.ELEMENT_NODE) {
×
359
            final ElementImpl last = stack.pop();
×
360
            broker.endElement(last, currentPath, null);
×
361
            currentPath.removeLastComponent();
×
362
            setPrevious(last);
×
363
        }
364
    }
×
365

366
    private void setPrevious(final StoredNode previous) {
367
        if(prevNode != null && (prevNode.getNodeType() == Node.TEXT_NODE || prevNode.getNodeType() == Node.COMMENT_NODE || prevNode.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)) {
×
368
            if(previous == null || prevNode.getNodeType() != previous.getNodeType()) {
×
369
                prevNode.clear();
×
370
            }
371
        }
372
        prevNode = previous;
×
373
    }
×
374
}
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