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

evolvedbinary / elemental / 1020

01 May 2025 02:30AM UTC coverage: 56.421% (+0.01%) from 56.409%
1020

push

circleci

adamretter
[feature] Improve README.md badges

28459 of 55847 branches covered (50.96%)

Branch coverage included in aggregate %.

77483 of 131924 relevant lines covered (58.73%)

0.59 hits per line

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

68.35
/exist-core/src/main/java/org/exist/dom/memtree/DocumentImpl.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.exist.Database;
52
import org.exist.EXistException;
53
import org.exist.Namespaces;
54
import org.exist.dom.NodeListImpl;
55
import org.exist.dom.QName;
56
import org.exist.dom.QName.IllegalQNameException;
57
import org.exist.dom.memtree.reference.*;
58
import org.exist.dom.persistent.NodeProxy;
59
import org.exist.numbering.NodeId;
60
import org.exist.numbering.NodeIdFactory;
61
import org.exist.storage.BrokerPool;
62
import org.exist.storage.DBBroker;
63
import org.exist.storage.ElementValue;
64
import org.exist.storage.serializers.Serializer;
65
import org.exist.util.hashtable.NamePool;
66
import org.exist.util.serializer.AttrList;
67
import org.exist.util.serializer.Receiver;
68
import org.exist.xmldb.XmldbURI;
69
import org.exist.xquery.Expression;
70
import org.exist.xquery.NodeTest;
71
import org.exist.xquery.XPathException;
72
import org.exist.xquery.XQueryContext;
73
import org.exist.xquery.value.Sequence;
74
import org.exist.xquery.value.Type;
75
import org.w3c.dom.*;
76
import org.xml.sax.SAXException;
77

78
import javax.annotation.Nullable;
79
import javax.xml.XMLConstants;
80
import java.util.Arrays;
81
import java.util.Objects;
82
import java.util.concurrent.atomic.AtomicLong;
83

84
import static java.nio.charset.StandardCharsets.UTF_8;
85
import static org.exist.dom.QName.Validity.ILLEGAL_FORMAT;
86

87

88
/**
89
 * An in-memory implementation of Document.
90
 *
91
 * Nodes are stored in a series of arrays which are all indexed by the {@code nodeNum} (Node Number).
92
 * Nodes are stored into the arrays in a Depth First Preorder (Root, Left, Right) Traversal.
93
 * The {@code nodeNum} starts at 0, the array {@link #nodeKind} indicates the type of the Node.
94
 * For example, for a complete XML Document, when {@code nodeNum == 0}, then {@code nodeKind[nodeNum] == org.w3c.dom.Node.DOCUMENT_NODE}.
95
 *
96
 * The array {@link #treeLevel} indicates at what level in the tree the node appears, the root of the tree is 0.
97
 * For example, for a complete XML Document, when {@code nodeNum == 0}, then {@code treeLevel[nodeNum] == 0}.
98
 *
99
 * The array {@link #next} gives the {@code nodeNum} of the next node in the tree,
100
 * for example {@code int nextNodeNum = next[nodeNum]}.
101
 *
102
 * The following arrays hold the data of the nodes themselves:
103
 *  * {@link #namespaceParent}
104
 *  * {@link #namespaceCode}
105
 *  * {@link #nodeName}
106
 *  * {@link #alpha}
107
 *  * {@link #alphaLen}
108
 *  * {@link #characters}
109
 *  * {@link #nodeId}
110
 *  * {@link #attrName}
111
 *  * {@link #attrType}
112
 *  * {@link #attrNodeId}
113
 *  * {@link #attrParent}
114
 *  * {@link #attrValue}
115
 *  * {@link #references}
116
 *
117
 * This implementation stores all node data in the document object. Nodes from another document, i.e. a persistent document in the database, can be
118
 * stored as reference nodes, i.e. the nodes are not copied into this document object. Instead a reference is inserted which will only be expanded
119
 * during serialization.
120
 */
121
public class DocumentImpl extends NodeImpl<DocumentImpl> implements Document {
122

123
    private static final AtomicLong nextDocId = new AtomicLong();
1✔
124

125
    private static final int NODE_SIZE = 16;
126
    private static final int ATTR_SIZE = 8;
127
    private static final int CHAR_BUF_SIZE = 256;
128
    private static final int REF_SIZE = 8;
1✔
129

130
    protected DocumentType docType = null;
1✔
131

132
    // holds the node type of a node
133
    protected short[] nodeKind = null;
1✔
134

135
    // the tree level of a node
136
    protected short[] treeLevel;
137

138
    // the node number of the next sibling
139
    protected int[] next;
140

141
    // pointer into the namePool
142
    protected QName[] nodeName;
143

144
    protected NodeId[] nodeId;
145

146
    //alphanumeric content
147
    protected int[] alpha;
148
    protected int[] alphaLen;
149
    protected char[] characters = null;
1✔
150
    protected int nextChar = 0;
1✔
151

152
    // attributes
153
    protected QName[] attrName;
154
    protected int[] attrType;
155
    protected NodeId[] attrNodeId;
156
    protected int[] attrParent;
157
    protected String[] attrValue;
158
    protected int nextAttr = 0;
1✔
159

160
    // namespaces
161
    protected int[] namespaceParent = null;
1✔
162
    protected QName[] namespaceCode = null;
1✔
163
    protected int nextNamespace = 0;
1✔
164

165
    // the current number of nodes in the doc
166
    protected int size = 1;
1✔
167

168
    protected int documentRootNode = -1;
1✔
169

170
    protected String documentURI = null;
1✔
171

172
    // reference nodes (link to an external, persistent document fragment)
173
    protected NodeProxy[] references = null;
1✔
174
    protected int nextReferenceIdx = 0;
1✔
175
    // end reference nodes
176

177

178
    protected XQueryContext context;
179
    protected final boolean explicitlyCreated;
180
    protected final long docId;
181
    private Database db = null;
1✔
182
    protected NamePool namePool;
183

184
    boolean replaceAttribute = false;
1✔
185

186

187
    public DocumentImpl(final XQueryContext context, final boolean explicitlyCreated) {
188
        this(null, context, explicitlyCreated);
×
189
    }
×
190

191

192
    public DocumentImpl(final Expression expression, final XQueryContext context, final boolean explicitlyCreated) {
193
        super(expression, null, 0);
1✔
194
        this.context = context;
1✔
195
        this.explicitlyCreated = explicitlyCreated;
1✔
196
        this.docId = nextDocId.incrementAndGet();
1✔
197
        if(context == null) {
1✔
198
            namePool = new NamePool();
1✔
199
        } else {
1✔
200
            db = context.getDatabase();
1✔
201
            namePool = context.getSharedNamePool();
1✔
202
        }
203
    }
1✔
204

205
    private Database getDatabase() {
206
        if(db == null) {
1!
207
            try {
208
                db = BrokerPool.getInstance();
×
209
            } catch(final EXistException e) {
×
210
                throw new RuntimeException(e);
×
211
            }
212
        }
213
        return db;
1✔
214
    }
215

216
    private void init() {
217
        nodeKind = new short[NODE_SIZE];
1✔
218
        treeLevel = new short[NODE_SIZE];
1✔
219
        next = new int[NODE_SIZE];
1✔
220
        Arrays.fill(next, -1);
1✔
221
        nodeName = new QName[NODE_SIZE];
1✔
222
        nodeId = new NodeId[NODE_SIZE];
1✔
223
        alpha = new int[NODE_SIZE];
1✔
224
        alphaLen = new int[NODE_SIZE];
1✔
225
        Arrays.fill(alphaLen, -1);
1✔
226
        attrName = new QName[ATTR_SIZE];
1✔
227
        attrParent = new int[ATTR_SIZE];
1✔
228
        attrValue = new String[ATTR_SIZE];
1✔
229
        attrType = new int[ATTR_SIZE];
1✔
230
        attrNodeId = new NodeId[NODE_SIZE];
1✔
231
        treeLevel[0] = 0;
1✔
232
        nodeKind[0] = Node.DOCUMENT_NODE;
1✔
233
        document = this;
1✔
234
    }
1✔
235

236
    public void reset() {
237
        size = 0;
×
238
        nextChar = 0;
×
239
        nextAttr = 0;
×
240
        nextReferenceIdx = 0;
×
241
        references = null;
×
242
    }
×
243

244
    public int getSize() {
245
        return size;
1✔
246
    }
247

248
    public long getDocId() {
249
        return docId;
1✔
250
    }
251

252
    public boolean isExplicitlyCreated() {
253
        return explicitlyCreated;
1✔
254
    }
255

256
    public int addNode(final short kind, final short level, @Nullable final QName qname) {
257
        if (nodeKind == null) {
1✔
258
            init();
1✔
259
        }
260
        if (size == nodeKind.length) {
1✔
261
            grow();
1✔
262
        }
263
        nodeKind[size] = kind;
1✔
264
        treeLevel[size] = level;
1✔
265
        nodeName[size] = qname != null ? namePool.getSharedName(qname) : null;
1✔
266
        alpha[size] = -1; // undefined
1✔
267
        next[size] = -1;
1✔
268
        return (size++);
1✔
269
    }
270

271
    public void addChars(final int nodeNum, final char[] ch, final int start, final int len) {
272
        if(nodeKind == null) {
1!
273
            init();
×
274
        }
275
        if(characters == null) {
1✔
276
            characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE];
1✔
277
        } else if((nextChar + len) >= characters.length) {
1✔
278
            int newLen = (characters.length * 3) / 2;
1✔
279
            if(newLen < (nextChar + len)) {
1✔
280
                newLen = nextChar + len;
1✔
281
            }
282
            final char[] nc = new char[newLen];
1✔
283
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
284
            characters = nc;
1✔
285
        }
286
        alpha[nodeNum] = nextChar;
1✔
287
        alphaLen[nodeNum] = len;
1✔
288
        System.arraycopy(ch, start, characters, nextChar, len);
1✔
289
        nextChar += len;
1✔
290
    }
1✔
291

292
    public void addChars(final int nodeNum, final CharSequence s) {
293
        if(nodeKind == null) {
1!
294
            init();
×
295
        }
296
        int len = (s == null) ? 0 : s.length();
1!
297
        if(characters == null) {
1✔
298
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
1✔
299
        } else if((nextChar + len) >= characters.length) {
1✔
300
            int newLen = (characters.length * 3) / 2;
1✔
301
            if(newLen < (nextChar + len)) {
1✔
302
                newLen = nextChar + len;
1✔
303
            }
304
            final char[] nc = new char[newLen];
1✔
305
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
306
            characters = nc;
1✔
307
        }
308
        alpha[nodeNum] = nextChar;
1✔
309
        alphaLen[nodeNum] = len;
1✔
310
        for(int i = 0; i < len; i++) {
1✔
311
            characters[nextChar++] = s.charAt(i);
1✔
312
        }
313
    }
1✔
314

315
    public void appendChars(final int nodeNum, final char[] ch, final int start, final int len) {
316
        if(characters == null) {
1!
317
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
×
318
        } else if((nextChar + len) >= characters.length) {
1✔
319
            int newLen = (characters.length * 3) / 2;
1✔
320
            if(newLen < (nextChar + len)) {
1!
321
                newLen = nextChar + len;
×
322
            }
323
            final char[] nc = new char[newLen];
1✔
324
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
325
            characters = nc;
1✔
326
        }
327
        alphaLen[nodeNum] = alphaLen[nodeNum] + len;
1✔
328
        System.arraycopy(ch, start, characters, nextChar, len);
1✔
329
        nextChar += len;
1✔
330
    }
1✔
331

332
    public void appendChars(final int nodeNum, final CharSequence s) {
333
        final int len = s.length();
1✔
334
        if(characters == null) {
1!
335
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
×
336
        } else if((nextChar + len) >= characters.length) {
1!
337
            int newLen = (characters.length * 3) / 2;
×
338
            if(newLen < (nextChar + len)) {
×
339
                newLen = nextChar + len;
×
340
            }
341
            final char[] nc = new char[newLen];
×
342
            System.arraycopy(characters, 0, nc, 0, characters.length);
×
343
            characters = nc;
×
344
        }
345
        alphaLen[nodeNum] = alphaLen[nodeNum] + len;
1✔
346
        for(int i = 0; i < len; i++) {
1✔
347
            characters[nextChar++] = s.charAt(i);
1✔
348
        }
349
    }
1✔
350

351
    void addReferenceNode(final int nodeNum, final NodeProxy proxy) {
352
        if (nodeKind == null) {
1!
353
            init();
×
354
        }
355
        if (references == null || nextReferenceIdx == references.length) {
1!
356
            growReferences();
1✔
357
        }
358
        references[nextReferenceIdx] = proxy;
1✔
359
        alpha[nodeNum] = nextReferenceIdx++;
1✔
360
    }
1✔
361

362
    public boolean hasReferenceNodes() {
363
        return references != null && references[0] != null;
1!
364
    }
365

366
    public void replaceReferenceNode(final int nodeNum, final CharSequence ch) {
367
        nodeKind[nodeNum] = Node.TEXT_NODE;
1✔
368
        references[alpha[nodeNum]] = null;
1✔
369
        addChars(nodeNum, ch);
1✔
370
    }
1✔
371

372
    public int addAttribute(final int nodeNum, final QName qname, final String value, final int type) throws DOMException {
373
        if(nodeKind == null) {
1✔
374
            init();
1✔
375
        }
376
        if((nodeNum > 0) && !(nodeKind[nodeNum] == Node.ELEMENT_NODE || nodeKind[nodeNum] == NodeImpl.NAMESPACE_NODE)) {
1!
377
            throw (new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
×
378
                "err:XQTY0024: An attribute node cannot follow a node that is not an element or namespace node."));
×
379
        }
380
        int prevAttr = nextAttr - 1;
1✔
381
        int attrN;
382
        //Check if an attribute with the same qname exists in the parent element
383
        while((nodeNum > 0) && (prevAttr > -1) && (attrParent[prevAttr] == nodeNum)) {
1✔
384
            attrN = prevAttr--;
1✔
385
            final QName prevQn = attrName[attrN];
1✔
386
            if(prevQn.equals(qname)) {
1✔
387
                if(replaceAttribute) {
1!
388
                    attrValue[attrN] = value;
×
389
                    attrType[attrN] = type;
×
390
                    return attrN;
×
391
                } else {
392
                    throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
1✔
393
                        "err:XQDY0025: element has more than one attribute '" + qname + "'");
1✔
394
                }
395
            }
396
        }
397
        if(nextAttr == attrName.length) {
1✔
398
            growAttributes();
1✔
399
        }
400
        final QName attrQname = new QName(qname.getLocalPart(), qname.getNamespaceURI(), qname.getPrefix(), ElementValue.ATTRIBUTE);
1✔
401
        attrParent[nextAttr] = nodeNum;
1✔
402
        attrName[nextAttr] = namePool.getSharedName(attrQname);
1✔
403
        attrValue[nextAttr] = value;
1✔
404
        attrType[nextAttr] = type;
1✔
405
        if(alpha[nodeNum] < 0) {
1✔
406
            alpha[nodeNum] = nextAttr;
1✔
407
        }
408
        return (nextAttr++);
1✔
409
    }
410

411
    public int addNamespace(final int nodeNum, final QName qname) {
412
        if(nodeKind == null) {
1✔
413
            init();
1✔
414
        }
415
        if((namespaceCode == null) || (nextNamespace == namespaceCode.length)) {
1✔
416
            growNamespaces();
1✔
417
        }
418
        namespaceCode[nextNamespace] = namePool.getSharedName(qname);
1✔
419
        namespaceParent[nextNamespace] = nodeNum;
1✔
420
        if(alphaLen[nodeNum] < 0) {
1✔
421
            alphaLen[nodeNum] = nextNamespace;
1✔
422
        }
423
        return nextNamespace++;
1✔
424
    }
425

426
    public short getTreeLevel(final int nodeNum) {
427
        return treeLevel[nodeNum];
1✔
428
    }
429

430
    public int getLastNode() {
431
        return size - 1;
1✔
432
    }
433

434
    public short getNodeType(final int nodeNum) {
435
        if((nodeKind == null) || (nodeNum < 0)) {
1!
436
            return -1;
×
437
        }
438
        return nodeKind[nodeNum];
1✔
439
    }
440

441
    @Override
442
    public String getStringValue() {
443
        if(document == null) {
1!
444
            return "";
×
445
        }
446
        return super.getStringValue();
1✔
447
    }
448

449
    private void grow() {
450
        final int newSize = (size * 3) / 2;
1✔
451

452
        final short[] newNodeKind = new short[newSize];
1✔
453
        System.arraycopy(nodeKind, 0, newNodeKind, 0, size);
1✔
454
        nodeKind = newNodeKind;
1✔
455

456
        final short[] newTreeLevel = new short[newSize];
1✔
457
        System.arraycopy(treeLevel, 0, newTreeLevel, 0, size);
1✔
458
        treeLevel = newTreeLevel;
1✔
459

460
        final int[] newNext = new int[newSize];
1✔
461
        Arrays.fill(newNext, -1);
1✔
462
        System.arraycopy(next, 0, newNext, 0, size);
1✔
463
        next = newNext;
1✔
464

465
        final QName[] newNodeName = new QName[newSize];
1✔
466
        System.arraycopy(nodeName, 0, newNodeName, 0, size);
1✔
467
        nodeName = newNodeName;
1✔
468

469
        final NodeId[] newNodeId = new NodeId[newSize];
1✔
470
        System.arraycopy(nodeId, 0, newNodeId, 0, size);
1✔
471
        nodeId = newNodeId;
1✔
472

473
        final int[] newAlpha = new int[newSize];
1✔
474
        System.arraycopy(alpha, 0, newAlpha, 0, size);
1✔
475
        alpha = newAlpha;
1✔
476

477
        final int[] newAlphaLen = new int[newSize];
1✔
478
        Arrays.fill(newAlphaLen, -1);
1✔
479
        System.arraycopy(alphaLen, 0, newAlphaLen, 0, size);
1✔
480
        alphaLen = newAlphaLen;
1✔
481
    }
1✔
482

483
    private void growAttributes() {
484
        final int size = attrName.length;
1✔
485
        final int newSize = (size * 3) / 2;
1✔
486

487
        final QName[] newAttrName = new QName[newSize];
1✔
488
        System.arraycopy(attrName, 0, newAttrName, 0, size);
1✔
489
        attrName = newAttrName;
1✔
490

491
        final int[] newAttrParent = new int[newSize];
1✔
492
        System.arraycopy(attrParent, 0, newAttrParent, 0, size);
1✔
493
        attrParent = newAttrParent;
1✔
494

495
        final String[] newAttrValue = new String[newSize];
1✔
496
        System.arraycopy(attrValue, 0, newAttrValue, 0, size);
1✔
497
        attrValue = newAttrValue;
1✔
498

499
        final int[] newAttrType = new int[newSize];
1✔
500
        System.arraycopy(attrType, 0, newAttrType, 0, size);
1✔
501
        attrType = newAttrType;
1✔
502

503
        final NodeId[] newNodeId = new NodeId[newSize];
1✔
504
        System.arraycopy(attrNodeId, 0, newNodeId, 0, size);
1✔
505
        attrNodeId = newNodeId;
1✔
506
    }
1✔
507

508
    private void growReferences() {
509
        if(references == null) {
1!
510
            references = new NodeProxy[REF_SIZE];
1✔
511
        } else {
1✔
512
            final int size = references.length;
×
513
            final int newSize = (size * 3) / 2;
×
514
            final NodeProxy[] newReferences = new NodeProxy[newSize];
×
515
            System.arraycopy(references, 0, newReferences, 0, size);
×
516
            references = newReferences;
×
517
        }
518
    }
1✔
519

520
    private void growNamespaces() {
521
        if(namespaceCode == null) {
1✔
522
            namespaceCode = new QName[5];
1✔
523
            namespaceParent = new int[5];
1✔
524
        } else {
1✔
525
            final int size = namespaceCode.length;
1✔
526
            final int newSize = (size * 3) / 2;
1✔
527

528
            final QName[] newCodes = new QName[newSize];
1✔
529
            System.arraycopy(namespaceCode, 0, newCodes, 0, size);
1✔
530
            namespaceCode = newCodes;
1✔
531

532
            final int[] newParents = new int[newSize];
1✔
533
            System.arraycopy(namespaceParent, 0, newParents, 0, size);
1✔
534
            namespaceParent = newParents;
1✔
535
        }
536
    }
1✔
537

538
    public NodeImpl getAttribute(final int nodeNum) throws DOMException {
539
        return new AttrImpl(getExpression(), this, nodeNum);
1✔
540
    }
541

542
    public NodeImpl getNamespaceNode(final int nodeNum) throws DOMException {
543
        return new NamespaceNode(getExpression(), this, nodeNum);
1✔
544
    }
545

546
    public NodeImpl getNode(final int nodeNum) throws DOMException {
547
        if (nodeNum == 0) {
1✔
548
            return this;
1✔
549
        }
550

551
        if (nodeNum >= size) {
1!
552
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "node not found");
×
553
        }
554

555
        final NodeImpl<?> node;
556
        switch (nodeKind[nodeNum]) {
1!
557
            case Node.ELEMENT_NODE:
558
                node = new ElementImpl(getExpression(), this, nodeNum);
1✔
559
                break;
1✔
560

561
            case Node.TEXT_NODE:
562
                node = new TextImpl(getExpression(), this, nodeNum);
1✔
563
                break;
1✔
564

565
            case Node.COMMENT_NODE:
566
                node = new CommentImpl(getExpression(), this, nodeNum);
1✔
567
                break;
1✔
568

569
            case Node.PROCESSING_INSTRUCTION_NODE:
570
                node = new ProcessingInstructionImpl(getExpression(), this, nodeNum);
1✔
571
                break;
1✔
572

573
            case Node.CDATA_SECTION_NODE:
574
                node = new CDATASectionImpl(getExpression(), this, nodeNum);
1✔
575
                break;
1✔
576

577
            case NodeImpl.REFERENCE_NODE:
578
                final NodeProxy nodeProxy = references[alpha[nodeNum]];
1✔
579
                node = referenceFromNodeProxy(nodeNum, nodeProxy);
1✔
580
                break;
1✔
581

582
            default:
583
                throw new DOMException(DOMException.NOT_FOUND_ERR, "node not found");
×
584
        }
585
        return node;
1✔
586
    }
587

588
    private NodeImpl referenceFromNodeProxy(final int nodeNum, final NodeProxy nodeProxy) {
589
        final NodeImpl<?> node;
590
        switch (nodeProxy.getNodeType()) {
1!
591
            case Node.ELEMENT_NODE:
592
                node = new ElementReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
593
                break;
1✔
594

595
            case Node.TEXT_NODE:
596
                node = new TextReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
597
                break;
1✔
598

599
            case Node.PROCESSING_INSTRUCTION_NODE:
600
                node = new ProcessingInstructionReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
601
                break;
1✔
602

603
            case Node.COMMENT_NODE:
604
                node = new CommentReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
605
                break;
1✔
606

607
            default:
608
                throw new DOMException(DOMException.NOT_FOUND_ERR, "reference node not found");
×
609
        }
610

611
        return node;
1✔
612
    }
613

614
    public NodeImpl getLastAttr() {
615
        if(nextAttr == 0) {
×
616
            return null;
×
617
        }
618
        return new AttrImpl(getExpression(), this, nextAttr - 1);
×
619
    }
620

621
    @Override
622
    public Node getParentNode() {
623
        return null;
1✔
624
    }
625

626
    @Override
627
    public DocumentType getDoctype() {
628
        return docType;
1✔
629
    }
630

631
    public void setDoctype(final DocumentType docType) {
632
        this.docType = docType;
1✔
633
    }
1✔
634

635
    @Override
636
    public DOMImplementation getImplementation() {
637
        return new DOMImplementationImpl(getExpression());
×
638
    }
639

640
    @Override
641
    public Element getDocumentElement() {
642
        if(size == 1) {
1✔
643
            return null;
1✔
644
        }
645
        int nodeNum = 1;
1✔
646
        while(nodeKind[nodeNum] != Node.ELEMENT_NODE) {
1✔
647
            if(next[nodeNum] < nodeNum) {
1✔
648
                return null;
1✔
649
            }
650
            nodeNum = next[nodeNum];
1✔
651
        }
652
        return (Element)getNode(nodeNum);
1✔
653
    }
654

655
    @Override
656
    public Node getFirstChild() {
657
        if(size > 1) {
1!
658
            return getNode(1);
1✔
659
        }
660
        return null;
×
661
    }
662

663
    @Override
664
    public Node getLastChild() {
665
        return getFirstChild();
×
666
    }
667

668
    public int getAttributesCountFor(final int nodeNumber) {
669
        int count = 0;
1✔
670
        int attr = alpha[nodeNumber];
1✔
671
        if(-1 < attr) {
1✔
672
            while((attr < nextAttr) && (attrParent[attr++] == nodeNumber)) {
1✔
673
                ++count;
1✔
674
            }
675
        }
676
        return count;
1✔
677
    }
678

679
    public int getNamespacesCountFor(final int nodeNumber) {
680
        int count = 0;
1✔
681
        int ns = alphaLen[nodeNumber];
1✔
682
        if(-1 < ns) {
1✔
683
            while((ns < nextNamespace) && (namespaceParent[ns++] == nodeNumber)) {
1!
684
                ++count;
1✔
685
            }
686
        }
687
        return count;
1✔
688
    }
689

690
    public int getChildCountFor(final int nr) {
691
        int count = 0;
1✔
692
        int nextNode = getFirstChildFor(nr);
1✔
693
        while(nextNode > nr) {
1✔
694
            ++count;
1✔
695
            nextNode = next[nextNode];
1✔
696
        }
697
        return count;
1✔
698
    }
699

700
    public int getFirstChildFor(final int nodeNumber) {
701
        if (nodeNumber == 0) {
1✔
702
            // optimisation for document-node
703
            if (size > 1) {
1!
704
                return 1;
1✔
705
            } else {
706
                return -1;
×
707
            }
708
        }
709

710
        final short level = treeLevel[nodeNumber];
1✔
711
        final int nextNode = nodeNumber + 1;
1✔
712
        if((nextNode < size) && (treeLevel[nextNode] > level)) {
1✔
713
            return nextNode;
1✔
714
        }
715
        return -1;
1✔
716
    }
717

718
    public int getNextSiblingFor(final int nodeNumber) {
719
        final int nextNr = next[nodeNumber];
1✔
720
        return nextNr < nodeNumber ? -1 : nextNr;
1✔
721
    }
722

723
    public int getParentNodeFor(final int nodeNumber) {
724
        int nextNode = next[nodeNumber];
1✔
725
        while(nextNode > nodeNumber) {
1✔
726
            nextNode = next[nextNode];
1✔
727
        }
728
        return nextNode;
1✔
729
    }
730

731
    @Override
732
    public void selectChildren(final NodeTest test, final Sequence result) throws XPathException {
733
        if(size == 1) {
1!
734
            return;
×
735
        }
736
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
737
        while(next != null) {
1✔
738
            if(test.matches(next)) {
1✔
739
                result.add(next);
1✔
740
            }
741
            next = (NodeImpl) next.getNextSibling();
1✔
742
        }
743
    }
1✔
744

745
    @Override
746
    public void selectDescendants(final boolean includeSelf, final NodeTest test, final Sequence result)
747
        throws XPathException {
748
        if(includeSelf && test.matches(this)) {
1!
749
            result.add(this);
×
750
        }
751
        if(size == 1) {
1✔
752
            return;
1✔
753
        }
754
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
755
        while(next != null) {
1✔
756
            if(test.matches(next)) {
1✔
757
                result.add(next);
1✔
758
            }
759
            next.selectDescendants(includeSelf, test, result);
1✔
760
            next = (NodeImpl) next.getNextSibling();
1✔
761
        }
762
    }
1✔
763

764
    @Override
765
    public void selectDescendantAttributes(final NodeTest test, final Sequence result)
766
        throws XPathException {
767
        if(size == 1) {
1!
768
            return;
×
769
        }
770
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
771
        while(next != null) {
1✔
772
            if(test.matches(next)) {
1!
773
                result.add(next);
×
774
            }
775
            next.selectDescendantAttributes(test, result);
1✔
776
            next = (NodeImpl) next.getNextSibling();
1✔
777
        }
778
    }
1✔
779

780
    /**
781
     * Gets a specified node of this document.
782
     *
783
     * @param   id  the ID of the node to select
784
     * @return  the specified node of this document, or null if this document
785
     *          does not have the specified node
786
     */
787
    public NodeImpl selectById(final String id) {
788
        return selectById(id, false);
1✔
789
    }
790

791
    /**
792
     * Gets a specified node of this document.
793
     *
794
     * @param   id              the ID of the node to select
795
     * @param   typeConsidered  if true, this method should consider node
796
     *                          type attributes (i.e. <code>xsi:type="xs:ID"</code>);
797
     *                          if false, this method should not consider
798
     *                          node type attributes
799
     * @return  the specified node of this document, or null if this document
800
     *          does not have the specified node
801
     */
802
    public NodeImpl selectById(final String id, final boolean typeConsidered) {
803
        if(size == 1) {
1!
804
            return null;
×
805
        }
806
        expand();
1✔
807
        final ElementImpl root = (ElementImpl) getDocumentElement();
1✔
808
        if (hasIdAttribute(root.getNodeNumber(), id)) {
1✔
809
            return root;
1✔
810
        }
811
        final int treeLevel = this.treeLevel[root.getNodeNumber()];
1✔
812
        int nextNode = root.getNodeNumber();
1✔
813
        while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) {
1!
814
            if (document.nodeKind[nextNode] == Node.ELEMENT_NODE) {
1✔
815
                if (hasIdAttribute(nextNode, id)) {
1✔
816
                    return getNode(nextNode);
1✔
817
                } else if (hasIdTypeAttribute(nextNode, id)) {
1✔
818
                    return typeConsidered ? (NodeImpl) getNode(nextNode).getParentNode() : getNode(nextNode);
1✔
819
                } else if (getNode(nextNode).getNodeName().equalsIgnoreCase("id") &&
1✔
820
                        getNode(nextNode).getStringValue().equals(id)) {
1✔
821
                    return typeConsidered ? (NodeImpl) getNode(nextNode).getParentNode() : getNode(nextNode);
1✔
822
                }
823
            }
824
        }
825
        return null;
1✔
826
    }
827

828
    public NodeImpl selectByIdref(final String id) {
829
        if(size == 1) {
×
830
            return null;
×
831
        }
832
        expand();
×
833
        final ElementImpl root = (ElementImpl) getDocumentElement();
×
834
        AttrImpl attr = getIdrefAttribute(root.getNodeNumber(), id);
×
835
        if(attr != null) {
×
836
            return attr;
×
837
        }
838
        final int treeLevel = this.treeLevel[root.getNodeNumber()];
×
839
        int nextNode = root.getNodeNumber();
×
840
        while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) {
×
841
            if(document.nodeKind[nextNode] == Node.ELEMENT_NODE) {
×
842
                attr = getIdrefAttribute(nextNode, id);
×
843
                if(attr != null) {
×
844
                    return attr;
×
845
                }
846
            }
847
        }
848
        return null;
×
849
    }
850

851
    private boolean hasIdAttribute(final int nodeNumber, final String id) {
852
        int attr = document.alpha[nodeNumber];
1✔
853
        if(-1 < attr) {
1✔
854
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
1✔
855
                if((document.attrType[attr] == AttrImpl.ATTR_ID_TYPE) &&
1✔
856
                        id.equals(document.attrValue[attr])) {
1✔
857
                    return true;
1✔
858
                } else if (document.attrName[attr].getLocalPart().equals("id") &&
1✔
859
                           Objects.equals(document.attrValue[attr], id)) {
1✔
860
                    return true;
1✔
861
                }
862
                ++attr;
1✔
863
            }
864
        }
865
        return false;
1✔
866
    }
867

868
    private boolean hasIdTypeAttribute(final int nodeNumber, final String id) {
869
        int attr = document.alpha[nodeNumber];
1✔
870
        if(-1 < attr) {
1✔
871
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
1✔
872
                if (document.attrName[attr].getStringValue().equals(Namespaces.XSI_TYPE_QNAME.getStringValue()) &&
1✔
873
                        document.attrValue[attr].equals(Namespaces.XS_ID_QNAME.getStringValue()) &&
1!
874
                        document.getNode(nodeNumber).getStringValue().equals(id)) {
1!
875
                    return true;
1✔
876
                }
877
                ++attr;
1✔
878
            }
879
        }
880
        return false;
1✔
881
    }
882

883
    private AttrImpl getIdrefAttribute(final int nodeNumber, final String id) {
884
        int attr = document.alpha[nodeNumber];
×
885
        if(-1 < attr) {
×
886
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
×
887
                if((document.attrType[attr] == AttrImpl.ATTR_IDREF_TYPE) &&
×
888
                    id.equals(document.attrValue[attr])) {
×
889
                    return new AttrImpl(getExpression(), this, attr);
×
890
                }
891
                ++attr;
×
892
            }
893
        }
894
        return null;
×
895
    }
896

897
    @Override
898
    public boolean matchChildren(final NodeTest test) throws XPathException {
899
        if(size == 1) {
×
900
            return false;
×
901
        }
902
        NodeImpl next = (NodeImpl) getFirstChild();
×
903
        while(next != null) {
×
904
            if(test.matches(next)) {
×
905
                return true;
×
906
            }
907
            next = (NodeImpl) next.getNextSibling();
×
908
        }
909
        return false;
×
910
    }
911

912
    @Override
913
    public boolean matchDescendants(final boolean includeSelf, final NodeTest test) throws XPathException {
914
        if(includeSelf && test.matches(this)) {
×
915
            return true;
×
916
        }
917
        if(size == 1) {
×
918
            return true;
×
919
        }
920
        NodeImpl next = (NodeImpl) getFirstChild();
×
921
        while(next != null) {
×
922
            if(test.matches(next)) {
×
923
                return true;
×
924
            }
925
            if(next.matchDescendants(includeSelf, test)) {
×
926
                return true;
×
927
            }
928
            next = (NodeImpl) next.getNextSibling();
×
929
        }
930
        return false;
×
931
    }
932

933
    @Override
934
    public boolean matchDescendantAttributes(final NodeTest test) throws XPathException {
935
        if(size == 1) {
×
936
            return false;
×
937
        }
938
        NodeImpl next = (NodeImpl) getFirstChild();
×
939
        while(next != null) {
×
940
            if(test.matches(next)) {
×
941
                return true;
×
942
            }
943
            if(next.matchDescendantAttributes(test)) {
×
944
                return true;
×
945
            }
946
            next = (NodeImpl) next.getNextSibling();
×
947
        }
948
        return false;
×
949
    }
950

951
    @Override
952
    public Element createElement(final String tagName) throws DOMException {
953
        final QName qname;
954
        try {
955
            if (getContext() != null) {
×
956
                qname = QName.parse(getContext(), tagName);
×
957
            } else {
×
958
                qname = new QName(tagName);
×
959
            }
960
        } catch(final IllegalQNameException e) {
×
961
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
962
        }
963

964
        // check the QName is valid for use
965
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
966
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
967
        }
968

969
        final int nodeNum = addNode(Node.ELEMENT_NODE, (short) 1, qname);
×
970
        return new ElementImpl(getExpression(), this, nodeNum);
×
971
    }
972

973
    @Override
974
    public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException {
975
        final QName qname;
976
        try {
977
            if (getContext() != null) {
×
978
                qname = QName.parse(getContext(), qualifiedName, namespaceURI);
×
979
            } else {
×
980
                qname = QName.parse(namespaceURI, qualifiedName);
×
981
            }
982
        } catch(final IllegalQNameException e) {
×
983
            final short errCode;
984
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
985
                errCode = DOMException.NAMESPACE_ERR;
×
986
            } else {
×
987
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
988
            }
989
            throw new DOMException(errCode, "qualified name is invalid");
×
990
        }
991

992
        // check the QName is valid for use
993
        final byte validity = qname.isValid(false);
×
994
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
995
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
996
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
997
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
998
        }
999

1000
        final int nodeNum = addNode(Node.ELEMENT_NODE, (short) 1, qname);
×
1001
        return new ElementImpl(getExpression(), this, nodeNum);
×
1002
    }
1003

1004
    @Override
1005
    public DocumentFragment createDocumentFragment() {
1006
        return new DocumentFragmentImpl(getExpression());
×
1007
    }
1008

1009
    @Override
1010
    public Text createTextNode(final String data) {
1011
        return null;
×
1012
    }
1013

1014
    @Override
1015
    public Comment createComment(final String data) {
1016
        return null;
×
1017
    }
1018

1019
    @Override
1020
    public CDATASection createCDATASection(final String data) throws DOMException {
1021
        return null;
×
1022
    }
1023

1024
    @Override
1025
    public ProcessingInstruction createProcessingInstruction(final String target, final String data)
1026
            throws DOMException {
1027
        return null;
×
1028
    }
1029

1030
    @Override
1031
    public Attr createAttribute(final String name) throws DOMException {
1032
        final QName qname;
1033
        try {
1034
            if(getContext() != null) {
×
1035
                qname = QName.parse(getContext(), name);
×
1036
            } else {
×
1037
                qname = new QName(name);
×
1038
            }
1039
        } catch (final IllegalQNameException e) {
×
1040
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1041
        }
1042

1043
        // check the QName is valid for use
1044
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
1045
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1046
        }
1047

1048
        // TODO(AR) implement this!
1049
        throw unsupported();
×
1050
    }
1051

1052
    @Override
1053
    public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException {
1054
        final QName qname;
1055
        try {
1056
            if(getContext() != null) {
×
1057
                qname = QName.parse(getContext(), qualifiedName, namespaceURI);
×
1058
            } else {
×
1059
                qname = QName.parse(namespaceURI, qualifiedName);
×
1060
            }
1061
        } catch (final IllegalQNameException e) {
×
1062
            final short errCode;
1063
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1064
                errCode = DOMException.NAMESPACE_ERR;
×
1065
            } else {
×
1066
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
1067
            }
1068
            throw new DOMException(errCode, "qualified name is invalid");
×
1069
        }
1070

1071
        // check the QName is valid for use
1072
        final byte validity = qname.isValid(false);
×
1073
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
1074
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
1075
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1076
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
1077
        }
1078

1079
        // TODO(AR) implement this!
1080
        throw unsupported();
×
1081
    }
1082

1083
    @Override
1084
    public EntityReference createEntityReference(final String name) throws DOMException {
1085
        return null;
×
1086
    }
1087

1088
    @Override
1089
    public NodeList getElementsByTagName(final String tagname) {
1090
        if(tagname != null && tagname.equals(QName.WILDCARD)) {
1!
1091
            return getElementsByTagName(new QName.WildcardLocalPartQName(XMLConstants.DEFAULT_NS_PREFIX));
×
1092
        } else {
1093
            final QName qname;
1094
            try {
1095
                if (document.getContext() != null) {
1!
1096
                    qname = QName.parse(document.context, tagname);
×
1097

1098
                } else {
×
1099
                    qname = new QName(tagname);
1✔
1100
                }
1101
            } catch (final IllegalQNameException e) {
1✔
1102
                throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1103
            }
1104
            return getElementsByTagName(qname);
1✔
1105
        }
1106
    }
1107

1108
    @Override
1109
    public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
1110
        final boolean wildcardNS = namespaceURI != null && namespaceURI.equals(QName.WILDCARD);
×
1111
        final boolean wildcardLocalPart = localName != null && localName.equals(QName.WILDCARD);
×
1112

1113
        if(wildcardNS && wildcardLocalPart) {
×
1114
            return getElementsByTagName(QName.WildcardQName.getInstance());
×
1115
        } else if(wildcardNS) {
×
1116
            return getElementsByTagName(new QName.WildcardNamespaceURIQName(localName));
×
1117
        } else if(wildcardLocalPart) {
×
1118
            return getElementsByTagName(new QName.WildcardLocalPartQName(namespaceURI));
×
1119
        } else {
1120
            final QName qname;
1121
            if (document.getContext() != null) {
×
1122
                try {
1123
                    qname = QName.parse(document.context, localName, namespaceURI);
×
1124
                } catch (final IllegalQNameException e) {
×
1125
                    throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1126
                }
1127
            } else {
1128
                qname = new QName(localName, namespaceURI);
×
1129
            }
1130
            return getElementsByTagName(qname);
×
1131
        }
1132
    }
1133

1134
    private NodeList getElementsByTagName(final QName qname) {
1135
        final NodeListImpl nl = new NodeListImpl();
1✔
1136
        for(int i = 1; i < size; i++) {
1✔
1137
            if(nodeKind[i] == Node.ELEMENT_NODE) {
1✔
1138
                final QName qn = nodeName[i];
1✔
1139
                if(qn.matches(qname)) {
1✔
1140
                    nl.add(getNode(i));
1✔
1141
                }
1142
            }
1143
        }
1144
        return nl;
1✔
1145
    }
1146

1147
    @Override
1148
    public Node importNode(final Node importedNode, final boolean deep) throws DOMException {
1149
        return null;
×
1150
    }
1151

1152
    @Override
1153
    public Element getElementById(final String elementId) {
1154
        return null;
×
1155
    }
1156

1157
    @Override
1158
    public DocumentImpl getOwnerDocument() {
1159
        return null;
1✔
1160
    }
1161

1162
    /**
1163
     * Copy the document fragment starting at the specified node to the given document builder.
1164
     *
1165
     * @param node node to provide document fragment
1166
     * @param receiver document builder
1167
     * @throws SAXException DOCUMENT ME!
1168
     */
1169
    public void copyTo(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1170
        copyTo(null, node, receiver);
1✔
1171
    }
1✔
1172

1173
    private void copyTo(@Nullable final Serializer serializer, NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1174
        final NodeImpl top = node;
1✔
1175

1176
        @Nullable NodeImpl nextNode;
1177
        while (node != null) {
1✔
1178
            if (node instanceof AbstractReferenceNodeImpl) {
1✔
1179
                if (serializer != null) {
1✔
1180
                    serializer.toReceiver(((AbstractReferenceNodeImpl) node).getNodeProxy(), false, false);
1✔
1181
                } else {
1✔
1182
                    receiver.addReferenceNode(document.references[document.alpha[node.nodeNumber]]);
1✔
1183
                }
1184
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1185

1186
            } else {
1✔
1187
                copyStartNode(node, receiver);
1✔
1188
                nextNode = (NodeImpl) node.getFirstChild();
1✔
1189
            }
1190

1191
            while (nextNode == null) {
1✔
1192
                if (!(node instanceof AbstractReferenceNodeImpl)) {
1✔
1193
                    copyEndNode(node, receiver);
1✔
1194

1195
                    if (top.nodeNumber == node.nodeNumber) {
1✔
1196
                        break;
1✔
1197
                    }
1198
                }
1199

1200
                //No nextNode if the top node is a Document node
1201
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1202
                if (nextNode == null) {
1✔
1203
                    node = (NodeImpl) node.getParentNode();
1✔
1204
                    if (node == null || top.nodeNumber == node.nodeNumber) {
1✔
1205
                        if (node != null) {
1✔
1206
                            copyEndNode(node, receiver);
1✔
1207
                        }
1208
                        break;
1✔
1209
                    }
1210
                }
1211
            }
1212

1213
            node = nextNode;
1✔
1214
        }
1215
    }
1✔
1216

1217
    private void copyStartNode(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1218
        final int nr = node.nodeNumber;
1✔
1219
        switch (node.getNodeType()) {
1✔
1220
            case Node.ELEMENT_NODE: {
1221
                final QName nodeName = document.nodeName[nr];
1✔
1222
                receiver.startElement(nodeName, null);
1✔
1223
                int attr = document.alpha[nr];
1✔
1224
                if(-1 < attr) {
1✔
1225
                    while((attr < document.nextAttr) && (document.attrParent[attr] == nr)) {
1✔
1226
                        final QName attrQName = document.attrName[attr];
1✔
1227
                        receiver.attribute(attrQName, attrValue[attr]);
1✔
1228
                        ++attr;
1✔
1229
                    }
1230
                }
1231
                int ns = document.alphaLen[nr];
1✔
1232
                if(-1 < ns) {
1✔
1233
                    while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) {
1!
1234
                        final QName nsQName = document.namespaceCode[ns];
1✔
1235
                        receiver.addNamespaceNode(nsQName);
1✔
1236
                        ++ns;
1✔
1237
                    }
1238
                }
1239
                break;
×
1240
            }
1241

1242
            case Node.TEXT_NODE:
1243
                receiver.characters(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1244
                break;
1✔
1245

1246
            case Node.CDATA_SECTION_NODE:
1247
                receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1248
                break;
1✔
1249

1250
            case Node.ATTRIBUTE_NODE:
1251
                final QName attrQName = document.attrName[nr];
1✔
1252
                receiver.attribute(attrQName, attrValue[nr]);
1✔
1253
                break;
1✔
1254

1255
            case Node.COMMENT_NODE:
1256
                receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1257
                break;
1✔
1258

1259
            case Node.PROCESSING_INSTRUCTION_NODE:
1260
                final QName piQName = document.nodeName[nr];
1✔
1261
                final String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1262
                receiver.processingInstruction(piQName.getLocalPart(), data);
1✔
1263
                break;
1✔
1264

1265
            case NodeImpl.NAMESPACE_NODE:
1266
                receiver.addNamespaceNode(document.namespaceCode[nr]);
1✔
1267
                break;
1268
        }
1269
    }
1✔
1270

1271
    private void copyEndNode(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1272
        if (node.getNodeType() == Node.ELEMENT_NODE) {
1✔
1273
            receiver.endElement(node.getQName());
1✔
1274
        }
1275
    }
1✔
1276

1277
    /**
1278
     * Expand all reference nodes in the current document, i.e. replace them by real nodes. Reference nodes are just pointers to nodes from other
1279
     * documents stored in the database. The XQuery engine uses reference nodes to speed up the creation of temporary doc fragments.
1280
     *
1281
     * This method creates a new copy of the document contents and expands all reference nodes.
1282
     *
1283
     * @throws DOMException DOCUMENT ME!
1284
     */
1285
    @Override
1286
    public void expand() throws DOMException {
1287
        // TODO(AR) see if we can get rid of expansion now we have org.exist.dom.memtree.reference.* classes
1288
        if(size == 0) {
1!
1289
            return;
×
1290
        }
1291
        final DocumentImpl newDoc = expandRefs(null);
1✔
1292
        copyDocContents(newDoc);
1✔
1293
    }
1✔
1294

1295
    // TODO(AR) see if we can get rid of expansion now we have org.exist.dom.memtree.reference.* classes
1296
    public DocumentImpl expandRefs(final NodeImpl rootNode) throws DOMException {
1297
        if(nextReferenceIdx == 0) {
1✔
1298
            computeNodeIds();
1✔
1299
            return this;
1✔
1300
        }
1301
        final MemTreeBuilder builder = new MemTreeBuilder(getExpression(), context);
1✔
1302
        final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(getExpression(), builder);
1✔
1303
        try (final DBBroker broker = getDatabase().getBroker()) {
1✔
1304
            final Serializer serializer = broker.borrowSerializer();
1✔
1305
            try {
1306
                serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
1✔
1307
                serializer.setReceiver(receiver);
1✔
1308

1309
                builder.startDocument();
1✔
1310
                NodeImpl node = (rootNode == null) ? (NodeImpl) getFirstChild() : rootNode;
1!
1311
                while (node != null) {
1✔
1312
                    copyTo(serializer, node, receiver);
1✔
1313
                    node = (NodeImpl) node.getNextSibling();
1✔
1314
                }
1315
                receiver.endDocument();
1✔
1316
            } finally {
1✔
1317
                broker.returnSerializer(serializer);
1✔
1318
            }
1319
        } catch (final SAXException | EXistException e) {
×
1320
            throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage());
×
1321
        }
1322
        final DocumentImpl newDoc = builder.getDocument();
1✔
1323
        newDoc.computeNodeIds();
1✔
1324
        return newDoc;
1✔
1325
    }
1326

1327
    public NodeImpl getNodeById(final NodeId id) {
1328
        expand();
×
1329
        for(int i = 0; i < size; i++) {
×
1330
            if(id.equals(nodeId[i])) {
×
1331
                return getNode(i);
×
1332
            }
1333
        }
1334
        return null;
×
1335
    }
1336

1337
    private void computeNodeIds() {
1338
        if(nodeId[0] != null) {
1✔
1339
            return;
1✔
1340
        }
1341
        final NodeIdFactory nodeFactory = getDatabase().getNodeFactory();
1✔
1342
        nodeId[0] = nodeFactory.documentNodeId();
1✔
1343
        if(size == 1) {
1✔
1344
            return;
1✔
1345
        }
1346
        NodeId nextId = nodeFactory.createInstance();
1✔
1347
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
1348
        while(next != null) {
1✔
1349
            computeNodeIds(nextId, next.nodeNumber);
1✔
1350
            next = (NodeImpl) next.getNextSibling();
1✔
1351
            nextId = nextId.nextSibling();
1✔
1352
        }
1353
    }
1✔
1354

1355
    private void computeNodeIds(final NodeId id, final int nodeNum) {
1356
        nodeId[nodeNum] = id;
1✔
1357
        if(nodeKind[nodeNum] == Node.ELEMENT_NODE) {
1✔
1358
            NodeId nextId = id.newChild();
1✔
1359
            int attr = document.alpha[nodeNum];
1✔
1360
            if(-1 < attr) {
1✔
1361
                while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNum)) {
1✔
1362
                    attrNodeId[attr] = nextId;
1✔
1363
                    nextId = nextId.nextSibling();
1✔
1364
                    ++attr;
1✔
1365
                }
1366
            }
1367
            int nextNode = getFirstChildFor(nodeNum);
1✔
1368
            while(nextNode > nodeNum) {
1✔
1369
                computeNodeIds(nextId, nextNode);
1✔
1370
                nextNode = document.next[nextNode];
1✔
1371
                if(nextNode > nodeNum) {
1✔
1372
                    nextId = nextId.nextSibling();
1✔
1373
                }
1374
            }
1375
        }
1376
    }
1✔
1377

1378
    /**
1379
     * DOCUMENT ME!
1380
     *
1381
     * @param newDoc
1382
     */
1383
    private void copyDocContents(final DocumentImpl newDoc) {
1384
        namePool = newDoc.namePool;
1✔
1385
        nodeKind = newDoc.nodeKind;
1✔
1386
        treeLevel = newDoc.treeLevel;
1✔
1387
        next = newDoc.next;
1✔
1388
        nodeName = newDoc.nodeName;
1✔
1389
        nodeId = newDoc.nodeId;
1✔
1390
        alpha = newDoc.alpha;
1✔
1391
        alphaLen = newDoc.alphaLen;
1✔
1392
        characters = newDoc.characters;
1✔
1393
        nextChar = newDoc.nextChar;
1✔
1394
        attrName = newDoc.attrName;
1✔
1395
        attrNodeId = newDoc.attrNodeId;
1✔
1396
        attrParent = newDoc.attrParent;
1✔
1397
        attrValue = newDoc.attrValue;
1✔
1398
        attrType = newDoc.attrType;
1✔
1399
        nextAttr = newDoc.nextAttr;
1✔
1400
        namespaceParent = newDoc.namespaceParent;
1✔
1401
        namespaceCode = newDoc.namespaceCode;
1✔
1402
        nextNamespace = newDoc.nextNamespace;
1✔
1403
        size = newDoc.size;
1✔
1404
        documentRootNode = newDoc.documentRootNode;
1✔
1405
        references = newDoc.references;
1✔
1406
        nextReferenceIdx = newDoc.nextReferenceIdx;
1✔
1407
    }
1✔
1408

1409
    /**
1410
     * Stream the specified document fragment to a receiver. This method
1411
     * is called by the serializer to output in-memory nodes.
1412
     *
1413
     * @param serializer the serializer
1414
     * @param node node to be serialized
1415
     * @param receiver the receiveer
1416
     * @throws SAXException DOCUMENT ME
1417
     */
1418
    public void streamTo(final Serializer serializer, NodeImpl node, final Receiver receiver) throws SAXException {
1419
        final NodeImpl top = node;
1✔
1420

1421
        int level = node.getNodeType() == Node.DOCUMENT_NODE ? 0 : 1;
1✔
1422

1423
        while (node != null) {
1✔
1424

1425
            @Nullable NodeImpl nextNode;
1426
            if (node instanceof AbstractReferenceNodeImpl) {
1✔
1427
                serializer.toReceiver(((AbstractReferenceNodeImpl) node).getNodeProxy(), true, level == 1);
1✔
1428
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1429

1430
            } else {
1✔
1431
                startNode(node, receiver);
1✔
1432
                nextNode = (NodeImpl) node.getFirstChild();
1✔
1433
                level++;
1✔
1434
            }
1435

1436
            while (nextNode == null) {
1✔
1437
                if (!(node instanceof AbstractReferenceNodeImpl)) {
1✔
1438
                    endNode(node, receiver);
1✔
1439
                    level--;
1✔
1440

1441
                    if (top.nodeNumber == node.nodeNumber) {
1✔
1442
                        break;
1✔
1443
                    }
1444
                }
1445

1446
                //No nextNode if the top node is a Document node
1447
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1448
                if (nextNode == null) {
1✔
1449
                    node = (NodeImpl) node.getParentNode();
1✔
1450
                    if (node == null || top.nodeNumber == node.nodeNumber) {
1✔
1451
                        if (node != null) {
1✔
1452
                            endNode(node, receiver);
1✔
1453
                            level--;
1✔
1454
                        }
1455
                        break;
1✔
1456
                    }
1457
                }
1458
            }
1459

1460
            node = nextNode;
1✔
1461
        }
1462
    }
1✔
1463

1464
    private void startNode(final NodeImpl node, final Receiver receiver) throws SAXException {
1465
        final int nodeNumber = node.nodeNumber;
1✔
1466
        switch (node.getNodeType()) {
1!
1467
            case Node.ELEMENT_NODE:
1468
                final QName nodeName = node.getQName();
1✔
1469

1470
                //Output required namespace declarations
1471
                int ns = document.alphaLen[nodeNumber];
1✔
1472
                if (ns > -1) {
1✔
1473
                    while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nodeNumber)) {
1✔
1474
                        final QName nsQName = document.namespaceCode[ns];
1✔
1475
                        if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) {
1✔
1476
                            receiver.startPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX, nsQName.getNamespaceURI());
1✔
1477
                        } else {
1✔
1478
                            receiver.startPrefixMapping(nsQName.getLocalPart(), nsQName.getNamespaceURI());
1✔
1479
                        }
1480
                        ++ns;
1✔
1481
                    }
1482
                }
1483

1484
                //Create the attribute list
1485
                @Nullable final AttrList attribs = node.getAttrList();
1✔
1486
                receiver.startElement(nodeName, attribs);
1✔
1487
                break;
1✔
1488

1489
            case Node.TEXT_NODE:
1490
                receiver.characters(((TextImpl) node).getData());
1✔
1491
                break;
1✔
1492

1493
            case Node.ATTRIBUTE_NODE:
1494
                receiver.attribute(node.getQName(), ((Attr) node).getValue());
×
1495
                break;
×
1496

1497
            case Node.COMMENT_NODE:
1498
                final char[] commentData = ((Comment) node).getData().toCharArray();
1✔
1499
                receiver.comment(commentData, 0, commentData.length);
1✔
1500
                break;
1✔
1501

1502
            case Node.PROCESSING_INSTRUCTION_NODE:
1503
                receiver.processingInstruction(node.getQName().getLocalPart(), ((ProcessingInstruction) node).getData());
1✔
1504
                break;
1✔
1505

1506
            case Node.CDATA_SECTION_NODE:
1507
                final char[] cdataSectionData = ((CDATASection) node).getData().toCharArray();
1✔
1508
                receiver.cdataSection(cdataSectionData, 0, cdataSectionData.length);
1✔
1509
                break;
1510
        }
1511
    }
1✔
1512

1513
    private void endNode(final NodeImpl node, final Receiver receiver) throws SAXException {
1514
        if(node.getNodeType() == Node.ELEMENT_NODE) {
1✔
1515
            receiver.endElement(node.getQName());
1✔
1516
            //End all prefix mappings used for the element
1517
            final int nr = node.nodeNumber;
1✔
1518
            int ns = document.alphaLen[nr];
1✔
1519
            if(ns > -1) {
1✔
1520
                while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) {
1✔
1521
                    final QName nsQName = document.namespaceCode[ns];
1✔
1522
                    if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) {
1✔
1523
                        receiver.endPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX);
1✔
1524
                    } else {
1✔
1525
                        receiver.endPrefixMapping(nsQName.getLocalPart());
1✔
1526
                    }
1527
                    ++ns;
1✔
1528
                }
1529
            }
1530
        }
1531
    }
1✔
1532

1533
    public org.exist.dom.persistent.DocumentImpl makePersistent() throws XPathException {
1534
        if(size <= 1) {
×
1535
            return null;
×
1536
        }
1537
        return context.storeTemporaryDoc(this);
×
1538
    }
1539

1540
    // this is DOM specific
1541
    public int getChildCount() {
1542
        int count = 0;
1✔
1543
        int top = (size > 1) ? 1 : -1;
1!
1544
        while(top > 0) {
1✔
1545
            ++count;
1✔
1546
            top = getNextSiblingFor(top);
1✔
1547
        }
1548
        return count;
1✔
1549
    }
1550

1551
    @Override
1552
    public boolean hasChildNodes() {
1553
        return getChildCount() > 0;
1!
1554
    }
1555

1556
    @Override
1557
    public NodeList getChildNodes() {
1558
        if (size == 1) {
1✔
1559
            return new NodeListImpl(0);
1✔
1560
        }
1561

1562
        final NodeListImpl children = new NodeListImpl(1);  // most likely a single element!
1✔
1563
        int nextChildNodeNum = 1;
1✔
1564
        while (nextChildNodeNum > 0) {
1✔
1565
            final NodeImpl child = getNode(nextChildNodeNum);
1✔
1566
            children.add(child);
1✔
1567
            nextChildNodeNum = next[nextChildNodeNum];
1✔
1568
        }
1569

1570
        return children;
1✔
1571
    }
1572

1573
    @Override
1574
    public String getInputEncoding() {
1575
        return null;
×
1576
    }
1577

1578
    @Override
1579
    public String getXmlEncoding() {
1580
        return UTF_8.name();    //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1581
    }
1582

1583
    @Override
1584
    public boolean getXmlStandalone() {
1585
        return false;   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1586
    }
1587

1588
    @Override
1589
    public void setXmlStandalone(final boolean xmlStandalone) throws DOMException {
1590
    }
×
1591

1592
    @Override
1593
    public String getXmlVersion() {
1594
        return "1.0";   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1595
    }
1596

1597
    @Override
1598
    public void setXmlVersion(final String xmlVersion) throws DOMException {
1599
    }
×
1600

1601
    @Override
1602
    public boolean getStrictErrorChecking() {
1603
        return false;
×
1604
    }
1605

1606
    @Override
1607
    public void setStrictErrorChecking(final boolean strictErrorChecking) {
1608
    }
×
1609

1610
    @Override
1611
    public String getDocumentURI() {
1612
        return documentURI;
1✔
1613
    }
1614

1615
    @Override
1616
    public void setDocumentURI(final String documentURI) {
1617
        this.documentURI = documentURI;
1✔
1618
    }
1✔
1619

1620
    @Override
1621
    public Node adoptNode(final Node source) throws DOMException {
1622
        return null;
×
1623
    }
1624

1625
    @Override
1626
    public DOMConfiguration getDomConfig() {
1627
        return null;
×
1628
    }
1629

1630
    @Override
1631
    public void normalizeDocument() {
1632
    }
×
1633

1634
    @Override
1635
    public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName)
1636
        throws DOMException {
1637
        return null;
×
1638
    }
1639

1640
    public void setContext(final XQueryContext context) {
1641
        this.context = context;
×
1642
    }
×
1643

1644
    public XQueryContext getContext() {
1645
        return context;
1✔
1646
    }
1647

1648
    @Override
1649
    public String getBaseURI() {
1650
        final Element el = getDocumentElement();
1✔
1651
        if(el != null) {
1!
1652
            final String baseURI = getDocumentElement().getAttributeNS(Namespaces.XML_NS, "base");
1✔
1653
            if(baseURI != null) {
1!
1654
                return baseURI;
1✔
1655
            }
1656
        }
1657
        final String docURI = getDocumentURI();
×
1658
        if(docURI != null) {
×
1659
            return docURI;
×
1660
        } else {
1661
            if(context!=null && context.isBaseURIDeclared()) {
×
1662
                try {
1663
                    return context.getBaseURI().getStringValue();
×
1664
                } catch(final XPathException e) {
×
1665
                    //TODO : make something !
1666
                }
1667
            }
1668
            return XmldbURI.EMPTY_URI.toString();
×
1669
        }
1670
    }
1671

1672
    @Override
1673
    public int getItemType() {
1674
        return Type.DOCUMENT;
1✔
1675
    }
1676

1677
    @Override
1678
    public String toString() {
1679
        final StringBuilder result = new StringBuilder();
×
1680
        result.append("in-memory#");
×
1681
        result.append("document {");
×
1682
        if(size != 1) {
×
1683
            int nodeNum = 1;
×
1684
            while(true) {
×
1685
                result.append(getNode(nodeNum).toString());
×
1686
                if(next[nodeNum] < nodeNum) {
×
1687
                    break;
×
1688
                }
1689
                nodeNum = next[nodeNum];
×
1690
            }
1691
        }
1692
        result.append("} ");
×
1693
        return result.toString();
×
1694
    }
1695

1696
    @Override
1697
    public void selectAttributes(final NodeTest test, final Sequence result)
1698
        throws XPathException {
1699
    }
×
1700

1701
    @Override
1702
    public Node appendChild(final Node newChild) throws DOMException {
1703
        if(newChild.getNodeType() != Node.DOCUMENT_NODE && newChild.getOwnerDocument() != this) {
×
1704
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Owning document IDs do not match");
×
1705
        }
1706

1707
        if(newChild == this) {
×
1708
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1709
                    "Cannot append a document to itself");
×
1710
        }
1711

1712
        if(newChild.getNodeType() == DOCUMENT_NODE) {
×
1713
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1714
                    "A Document Node may not be appended to a Document Node");
×
1715
        }
1716

1717
        if(newChild.getNodeType() == ELEMENT_NODE && getDocumentElement() != null) {
×
1718
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1719
                    "A Document Node may only have a single document element");
×
1720
        }
1721

1722
        if(newChild.getNodeType() == DOCUMENT_TYPE_NODE && getDoctype() != null) {
×
1723
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1724
                    "A Document Node may only have a single document type");
×
1725
        }
1726

1727
        throw unsupported();
×
1728
    }
1729
}
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