• 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

62.42
/exist-core/src/main/java/org/exist/storage/serializers/Serializer.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.storage.serializers;
50

51
import java.io.IOException;
52
import java.io.StringWriter;
53
import java.io.Writer;
54
import java.net.URI;
55
import java.net.URISyntaxException;
56
import java.util.*;
57

58
import javax.annotation.Nullable;
59
import javax.xml.transform.OutputKeys;
60
import javax.xml.transform.Source;
61
import javax.xml.transform.Templates;
62
import javax.xml.transform.TransformerConfigurationException;
63
import javax.xml.transform.TransformerException;
64
import javax.xml.transform.URIResolver;
65
import javax.xml.transform.sax.SAXResult;
66
import javax.xml.transform.sax.SAXSource;
67
import javax.xml.transform.sax.SAXTransformerFactory;
68
import javax.xml.transform.sax.TemplatesHandler;
69
import javax.xml.transform.sax.TransformerHandler;
70
import javax.xml.transform.stream.StreamResult;
71
import javax.xml.transform.stream.StreamSource;
72

73
import com.evolvedbinary.j8fu.lazy.LazyVal;
74
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
75
import org.apache.logging.log4j.LogManager;
76
import org.apache.logging.log4j.Logger;
77
import org.exist.Namespaces;
78
import org.exist.dom.persistent.DocumentImpl;
79
import org.exist.dom.persistent.DocumentTypeImpl;
80
import org.exist.dom.persistent.NodeProxy;
81
import org.exist.dom.QName;
82
import org.exist.dom.persistent.XMLUtil;
83
import org.exist.indexing.IndexController;
84
import org.exist.indexing.MatchListener;
85
import org.exist.numbering.NodeId;
86
import org.exist.security.Permission;
87
import org.exist.security.PermissionDeniedException;
88
import org.exist.security.Subject;
89
import org.exist.storage.DBBroker;
90
import org.exist.util.Configuration;
91
import org.exist.util.MimeType;
92
import org.exist.util.serializer.AttrList;
93
import org.exist.util.serializer.Receiver;
94
import org.exist.util.serializer.ReceiverToSAX;
95
import org.exist.util.serializer.SAXSerializer;
96
import org.exist.util.serializer.SerializerPool;
97
import org.exist.xmldb.XmldbURI;
98
import org.exist.xquery.Constants;
99
import org.exist.xquery.XPathException;
100
import org.exist.xquery.XQueryContext;
101
import org.exist.xquery.value.Item;
102
import org.exist.xquery.value.NodeValue;
103
import org.exist.xquery.value.Sequence;
104
import org.exist.xquery.value.SequenceIterator;
105
import org.exist.xquery.value.Type;
106
import org.exist.xslt.TransformerFactoryAllocator;
107
import org.w3c.dom.*;
108
import org.xml.sax.*;
109
import org.xml.sax.ext.LexicalHandler;
110

111
/**
112
 * Serializer base class, used to serialize a document or document fragment
113
 * back to XML. A serializer may be obtained by calling DBBroker.getSerializer().
114
 * <p>
115
 * The class basically offers two overloaded methods: serialize()
116
 * and toSAX(). serialize() returns the XML as a string, while
117
 * toSAX() generates a stream of SAX events. The stream of SAX
118
 * events is passed to the ContentHandler set by setContentHandler().
119
 * <p>
120
 * Internally, both types of methods pass events to a {@link org.exist.util.serializer.Receiver}.
121
 * Subclasses thus have to implement the various serializeToReceiver() methods.
122
 * <p>
123
 * Output can be configured through properties. Property keys are defined in classes
124
 * {@link javax.xml.transform.OutputKeys} and {@link org.exist.storage.serializers.EXistOutputKeys}
125
 *
126
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
127
 */
128
public abstract class Serializer implements XMLReader {
129

130
    protected static final Logger LOG = LogManager.getLogger(Serializer.class);
1✔
131

132
    public static final String CONFIGURATION_ELEMENT_NAME = "serializer";
133
    public static final String OMIT_XML_DECLARATION_ATTRIBUTE = "omit-xml-declaration";
134
    public static final String PROPERTY_OMIT_XML_DECLARATION = "serialization.omit-xml-declaration";
135
    public static final String OMIT_ORIGINAL_XML_DECLARATION_ATTRIBUTE = "omit-original-xml-declaration";
136
    public static final String PROPERTY_OMIT_ORIGINAL_XML_DECLARATION = "serialization.omit-original-xml-declaration";
137
    public static final String OUTPUT_DOCTYPE_ATTRIBUTE = "output-doctype";
138
    public static final String PROPERTY_OUTPUT_DOCTYPE = "serialization.output-doctype";
139
    public static final String ENABLE_XINCLUDE_ATTRIBUTE = "enable-xinclude";
140
    public static final String PROPERTY_ENABLE_XINCLUDE = "serialization.enable-xinclude";
141
    public static final String ENABLE_XSL_ATTRIBUTE = "enable-xsl";
142
    public static final String PROPERTY_ENABLE_XSL = "serialization.enable-xsl";
143
    public static final String INDENT_ATTRIBUTE = "indent";
144
    public static final String PROPERTY_INDENT = "serialization.indent";
145
    public static final String COMPRESS_OUTPUT_ATTRIBUTE = "compress-output";
146
    public static final String PROPERTY_COMPRESS_OUTPUT = "serialization.compress-output";
147
    public static final String ADD_EXIST_ID_ATTRIBUTE = "add-exist-id";
148
    public static final String PROPERTY_ADD_EXIST_ID = "serialization.add-exist-id";
149
    public static final String TAG_MATCHING_ELEMENTS_ATTRIBUTE = "match-tagging-elements";
150
    public static final String PROPERTY_TAG_MATCHING_ELEMENTS = "serialization.match-tagging-elements";
151
    public static final String TAG_MATCHING_ATTRIBUTES_ATTRIBUTE = "match-tagging-attributes";
152
    public static final String PROPERTY_TAG_MATCHING_ATTRIBUTES = "serialization.match-tagging-attributes";
153
    public static final String PROPERTY_SESSION_ID = "serialization.session-id";
154

155
    // constants to configure the highlighting of matches in text and attributes
156
    public static final int TAG_NONE = 0x0;
157
    public static final int TAG_ELEMENT_MATCHES = 0x1;
158
    public static final int TAG_ATTRIBUTE_MATCHES = 0x2;
159
    public static final int TAG_BOTH = 0x3;
160

161
    public static final int EXIST_ID_NONE = 0;
162
    public static final int EXIST_ID_ELEMENT = 1;
163
    public static final int EXIST_ID_ALL = 2;
164

165
    protected int showId = EXIST_ID_NONE;
1✔
166

167
    public static final String GENERATE_DOC_EVENTS = "sax-document-events";
168
    public static final String ENCODING = "encoding";
169

170
    private static final QName ATTR_HITS_QNAME = new QName("hits", Namespaces.EXIST_NS, "exist");
1✔
171
    private static final QName ATTR_START_QNAME = new QName("start", Namespaces.EXIST_NS, "exist");
1✔
172
    private static final QName ATTR_COUNT_QNAME = new QName("count", Namespaces.EXIST_NS, "exist");
1✔
173
    private static final QName ELEM_RESULT_QNAME = new QName("result", Namespaces.EXIST_NS, "exist");
1✔
174
    private static final QName ATTR_SESSION_ID = new QName("session", Namespaces.EXIST_NS, "exist");
1✔
175
    private static final QName ATTR_COMPILATION_TIME_QNAME = new QName("compilation-time", Namespaces.EXIST_NS, "exist");
1✔
176
    private static final QName ATTR_EXECUTION_TIME_QNAME = new QName("execution-time", Namespaces.EXIST_NS, "exist");
1✔
177
    private static final QName ATTR_TYPE_QNAME = new QName("type", Namespaces.EXIST_NS, "exist");
1✔
178
    private static final QName ELEM_VALUE_QNAME = new QName("value", Namespaces.EXIST_NS, "exist");
1✔
179

180
    // required for XQJ/typed information implementation
181
    // -----------------------------------------
182
    private static final QName ELEM_DOC_QNAME = new QName("document", Namespaces.EXIST_NS, "exist");
1✔
183
    private static final QName ELEM_ATTR_QNAME = new QName("attribute", Namespaces.EXIST_NS, "exist");
1✔
184
    private static final QName ELEM_TEXT_QNAME = new QName("text", Namespaces.EXIST_NS, "exist");
1✔
185

186
    private static final QName ATTR_URI_QNAME = new QName("uri", Namespaces.EXIST_NS, "exist");
1✔
187
    private static final QName ATTR_TNS_QNAME = new QName("target-namespace", Namespaces.EXIST_NS, "exist");
1✔
188
    private static final QName ATTR_LOCAL_QNAME = new QName("local", Namespaces.EXIST_NS, "exist");
1✔
189
    private static final QName ATTR_PREFIX_QNAME = new QName("prefix", Namespaces.EXIST_NS, "exist");
1✔
190
    private static final QName ATTR_HAS_ELEMENT_QNAME = new QName("has-element", Namespaces.EXIST_NS, "exist");
1✔
191
    // -----------------------------------------
192

193

194
    protected final DBBroker broker;
195

196
    private @Nullable EntityResolver entityResolver = null;
1✔
197

198
    private @Nullable ErrorHandler errorHandler = null;
1✔
199

200
    private final LazyVal<SAXTransformerFactory> factory;
201
    protected boolean createContainerElements = false;
1✔
202

203
    private final Properties defaultOutputProperties = new Properties();
1✔
204
    protected final Properties outputProperties = new Properties();
1✔
205
    private @Nullable Templates templates = null;
1✔
206
    private @Nullable TransformerHandler xslHandler = null;
1✔
207
    private final XIncludeFilter xinclude;
208
    private final CustomMatchListenerFactory customMatchListeners;
209
    protected @Nullable Receiver receiver = null;
1✔
210
    private @Nullable LexicalHandler lexicalHandler = null;
1✔
211
    private @Nullable Int2ObjectMap<String> useCharacterMaps = null;
1✔
212
    private @Nullable Subject user = null;
1✔
213

214
    protected @Nullable XQueryContext.HttpContext httpContext = null;
1✔
215

216
    protected boolean documentStarted = false;
1✔
217

218
    public void setHttpContext(final XQueryContext.HttpContext httpContext) {
219
        this.httpContext = httpContext;
1✔
220
    }
1✔
221

222

223
    public Serializer(final DBBroker broker, final Configuration config) {
224
        this(broker, config, null);
×
225
    }
×
226

227
    public Serializer(final DBBroker broker, final Configuration config, final List<String> chainOfReceivers) {
1✔
228
        this.broker = broker;
1✔
229
        this.factory = new LazyVal<>(() -> TransformerFactoryAllocator.getTransformerFactory(broker.getBrokerPool()));
1✔
230

231
        this.xinclude = new XIncludeFilter(this);
1✔
232
        this.customMatchListeners = new CustomMatchListenerFactory(broker, config, chainOfReceivers);
1✔
233
        this.receiver = xinclude;
1✔
234

235
        defaultOutputProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, config.getProperty(PROPERTY_ENABLE_XSL, "no"));
1✔
236

237
        @Nullable String option = null;
1✔
238

239
        if ((option = (String) config.getProperty(PROPERTY_ENABLE_XINCLUDE)) != null) {
1✔
240
            defaultOutputProperties.setProperty(EXistOutputKeys.EXPAND_XINCLUDES, option);
1✔
241
        }
242

243
        if ((option = (String) config.getProperty(PROPERTY_INDENT)) != null) {
1✔
244
            defaultOutputProperties.setProperty(OutputKeys.INDENT, option);
1✔
245
        }
246

247
        if ((option = (String) config.getProperty(PROPERTY_COMPRESS_OUTPUT)) != null) {
1✔
248
            defaultOutputProperties.setProperty(EXistOutputKeys.COMPRESS_OUTPUT, option);
1✔
249
        }
250

251
        if ((option = (String) config.getProperty(PROPERTY_ADD_EXIST_ID)) != null) {
1✔
252
            defaultOutputProperties.setProperty(EXistOutputKeys.ADD_EXIST_ID, option);
1✔
253
        }
254

255
        boolean tagElements = true, tagAttributes = false;
1✔
256
        if ((option = (String) config.getProperty(PROPERTY_TAG_MATCHING_ELEMENTS)) != null) {
1✔
257
            tagElements = "yes".equals(option);
1✔
258
        }
259
        if ((option = (String) config.getProperty(PROPERTY_TAG_MATCHING_ATTRIBUTES)) != null) {
1✔
260
            tagAttributes = "yes".equals(option);
1✔
261
        }
262
        if (tagElements && tagAttributes) {
1!
263
            option = "both";
×
264
        } else if (tagElements) {
1✔
265
            option = "elements";
1✔
266
        } else if (tagAttributes) {
1!
267
            option = "attributes";
×
268
        } else {
×
269
            option = "none";
1✔
270
        }
271
        defaultOutputProperties.setProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, option);
1✔
272

273
        defaultOutputProperties.setProperty(GENERATE_DOC_EVENTS, "true");
1✔
274

275
        // NOTE(AR) copy the default output properties to output properties (which is mutable!)
276
        outputProperties.putAll(defaultOutputProperties);
1✔
277
    }
1✔
278

279
    public void setProperties(@Nullable Properties properties)
280
            throws SAXNotRecognizedException, SAXNotSupportedException {
281
        if (properties == null) {
1!
282
            return;
×
283
        }
284

285
        for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); ) {
1✔
286
            final String key = (String) e.nextElement();
1✔
287
            if (key.equals(Namespaces.SAX_LEXICAL_HANDLER)) {
1!
288
                lexicalHandler = (LexicalHandler) properties.get(key);
×
289
            } else if (key.equals(EXistOutputKeys.USE_CHARACTER_MAPS)) {
1✔
290
                useCharacterMaps = (Int2ObjectMap<String>) properties.get(key);
1✔
291
            } else {
1✔
292
                setProperty(key, properties.getProperty(key));
1✔
293
            }
294
        }
295
    }
1✔
296

297
    public void setProperties(@Nullable final HashMap<String, Object> table)
298
            throws SAXNotRecognizedException, SAXNotSupportedException {
299
        if (table == null) {
×
300
            return;
×
301
        }
302

303
        for (final Map.Entry<String, Object> entry : table.entrySet()) {
×
304
            setProperty(entry.getKey(), entry.getValue().toString());
×
305
        }
306
    }
×
307

308
    public void setProperty(final String prop, final Object value)
309
            throws SAXNotRecognizedException, SAXNotSupportedException {
310
        switch (prop) {
1!
311
            case Namespaces.SAX_LEXICAL_HANDLER:
312
                lexicalHandler = (LexicalHandler) value;
×
313
                break;
×
314
            case EXistOutputKeys.ADD_EXIST_ID:
315
                if ("element".equals(value)) {
1✔
316
                    showId = EXIST_ID_ELEMENT;
1✔
317
                } else if ("all".equals(value)) {
1✔
318
                    showId = EXIST_ID_ALL;
1✔
319
                } else {
1✔
320
                    showId = EXIST_ID_NONE;
1✔
321
                }
322
                break;
1✔
323
            default:
324
                outputProperties.put(prop, value);
1✔
325
                break;
326
        }
327

328
    }
1✔
329

330
    public String getProperty(final String key, String defaultValue) {
331
        return outputProperties.getProperty(key, defaultValue);
1✔
332
    }
333

334
    public boolean isStylesheetApplied() {
335
        return templates != null;
1!
336
    }
337

338
    protected int getHighlightingMode() {
339
        final String option =
1✔
340
                getProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, "elements");
1✔
341
        if ("both".equals(option) || "all".equals(option)) {
1!
342
            return TAG_BOTH;
×
343
        } else if ("elements".equals(option)) {
1✔
344
            return TAG_ELEMENT_MATCHES;
1✔
345
        } else if ("attributes".equals(option)) {
1!
346
            return TAG_ATTRIBUTE_MATCHES;
×
347
        } else {
348
            return TAG_NONE;
1✔
349
        }
350
    }
351

352
    /**
353
     * If an XSL stylesheet is present, plug it into
354
     * the chain.
355
     *
356
     * @param writer the writer
357
     */
358
    protected void applyXSLHandler(final Writer writer) {
359
        final StreamResult result = new StreamResult(writer);
×
360
        xslHandler.setResult(result);
×
361
        if ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
×
362
            xinclude.setReceiver(new ReceiverToSAX(xslHandler));
×
363
            receiver = xinclude;
×
364
        } else {
×
365
            receiver = new ReceiverToSAX(xslHandler);
×
366
        }
367
    }
×
368

369
    /**
370
     * Return my internal EntityResolver
371
     *
372
     * @return The entityResolver value
373
     */
374
    public @Nullable EntityResolver getEntityResolver() {
375
        return entityResolver;
×
376
    }
377

378
    public @Nullable ErrorHandler getErrorHandler() {
379
        return errorHandler;
×
380
    }
381

382
    /**
383
     * Set the current User. A valid user is required to
384
     * process XInclude elements.
385
     *
386
     * @param user the user
387
     */
388
    public void setUser(final Subject user) {
389
        this.user = user;
1✔
390
    }
1✔
391

392
    /**
393
     * Get the current User.
394
     *
395
     * @return the user
396
     */
397
    public @Nullable Subject getUser() {
398
        return user;
×
399
    }
400

401
    public boolean getFeature(final String name)
402
            throws SAXNotRecognizedException, SAXNotSupportedException {
403
        if (name.equals(Namespaces.SAX_NAMESPACES)
×
404
                || name.equals(Namespaces.SAX_NAMESPACES_PREFIXES)) {
×
405
            throw new SAXNotSupportedException(name);
×
406
        }
407
        throw new SAXNotRecognizedException(name);
×
408
    }
409

410
    public Object getProperty(final String name)
411
            throws SAXNotRecognizedException, SAXNotSupportedException {
412
        if (name.equals(Namespaces.SAX_LEXICAL_HANDLER)) {
×
413
            return lexicalHandler;
×
414
        }
415
        throw new SAXNotRecognizedException(name);
×
416
    }
417

418
    public @Nullable String getStylesheetProperty(final String name) {
419
        if (xslHandler != null) {
1!
420
            return xslHandler.getTransformer().getOutputProperty(name);
×
421
        }
422
        return null;
1✔
423
    }
424

425
    public void parse(final InputSource input) throws IOException, SAXException {
426
        // only system-ids are handled
427
        final String doc = input.getSystemId();
×
428
        if (doc == null) {
×
429
            throw new SAXException("source is not an eXist document");
×
430
        }
431
        parse(doc);
×
432
    }
×
433

434
    protected void setDocument(final DocumentImpl doc) {
435
        xinclude.setDocument(doc);
1✔
436
    }
1✔
437

438
    protected void setXQueryContext(@Nullable final XQueryContext context) {
439
        if (context != null) {
1✔
440
            xinclude.setModuleLoadPath(context.getModuleLoadPath());
1✔
441
        }
442
    }
1✔
443

444
    public void parse(final String systemId) throws IOException, SAXException {
445
        try {
446
            // try to load document from eXist
447
            //TODO: this systemId came from exist, so should be an unchecked create, right?
448
            final DocumentImpl doc = broker.getResource(XmldbURI.create(systemId), Permission.READ);
×
449
            if (doc == null) {
×
450
                throw new SAXException("document " + systemId + " not found in database");
×
451
            } else {
452
                LOG.debug("serializing {}", doc.getFileURI());
×
453
            }
454

455
            toSAX(doc);
×
456
        } catch (final PermissionDeniedException e) {
×
457
            throw new SAXException("permission denied");
×
458
        }
459
    }
×
460

461
    /**
462
     * Reset the class to its initial state.
463
     */
464
    public void reset() {
465
        this.showId = EXIST_ID_NONE;
1✔
466
        this.entityResolver = null;
1✔
467
        this.errorHandler = null;
1✔
468
        this.createContainerElements = false;
1✔
469
        this.outputProperties.clear();
1✔
470
        this.outputProperties.putAll(defaultOutputProperties);
1✔
471
        this.templates = null;
1✔
472
        this.xslHandler = null;
1✔
473
        this.xinclude.reset();
1✔
474
        this.receiver = xinclude;
1✔
475
        this.lexicalHandler = null;
1✔
476
        this.useCharacterMaps = null;
1✔
477
        this.user = null;
1✔
478
        this.httpContext = null;
1✔
479
        this.documentStarted = false;
1✔
480
    }
1✔
481

482
    public String serialize(final DocumentImpl doc) throws SAXException {
483
        final StringWriter writer = new StringWriter();
1✔
484
        serialize(doc, writer);
1✔
485
        return writer.toString();
1✔
486
    }
487

488
    /**
489
     * Serialize a document to the supplied writer.
490
     *
491
     * @param doc    the document
492
     * @param writer the output writer
493
     * @throws SAXException if an error occurs during serialization
494
     */
495
    public void serialize(final DocumentImpl doc, final Writer writer) throws SAXException {
496
        serialize(doc, writer, true);
1✔
497
    }
1✔
498

499
    public void serialize(final DocumentImpl doc, final Writer writer, final boolean prepareStylesheet) throws SAXException {
500
        if (prepareStylesheet) {
1!
501
            try {
502
                prepareStylesheets(doc);
1✔
503
            } catch (final TransformerConfigurationException e) {
1✔
504
                throw new SAXException(e.getMessage(), e);
×
505
            }
506
        }
507

508
        final SAXSerializer prettyPrinter;
509
        if (templates != null) {
1!
510
            applyXSLHandler(writer);
×
511
            prettyPrinter = null;
×
512
        } else {
×
513
            prettyPrinter = setPrettyPrinter(writer, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")), null, true);
1✔
514
        }
515

516
        try {
517
            serializeToReceiver(doc, true);
1✔
518
        } finally {
1✔
519
            if (prettyPrinter != null) {
1!
520
                releasePrettyPrinter(prettyPrinter);
1✔
521
            }
522
        }
523
    }
1✔
524

525
    public String serialize(final NodeValue n) throws SAXException {
526
        final StringWriter out = new StringWriter();
1✔
527
        serialize(n, out);
1✔
528
        return out.toString();
1✔
529
    }
530

531
    public void serialize(final NodeValue n, final Writer out) throws SAXException {
532
        serialize(n, out, true);
1✔
533
    }
1✔
534

535
    public void serialize(final NodeValue n, final Writer out, final boolean prepareStylesheet) throws SAXException {
536
        final Document doc;
537
        if (n.getItemType() == Type.DOCUMENT && !(n instanceof NodeProxy)) {
1!
538
            doc = (Document) n;
×
539
        } else {
×
540
            doc = n.getOwnerDocument();
1✔
541
        }
542

543
        if (prepareStylesheet) {
1!
544
            try {
545
                prepareStylesheets(doc);
1✔
546
            } catch (final TransformerConfigurationException e) {
1✔
547
                throw new SAXException(e.getMessage(), e);
×
548
            }
549
        }
550

551
        final SAXSerializer prettyPrinter;
552
        if (templates != null) {
1!
553
            applyXSLHandler(out);
×
554
            prettyPrinter = null;
×
555
        } else {
×
556
            prettyPrinter = setPrettyPrinter(out, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")),
1✔
557
                    n.getImplementationType() == NodeValue.PERSISTENT_NODE ? (NodeProxy) n : null, false);
1✔
558
        }
559

560
        try {
561
            serializeToReceiver(n, true);
1✔
562
        } finally {
1✔
563
            if (prettyPrinter != null) {
1!
564
                releasePrettyPrinter(prettyPrinter);
1✔
565
            }
566
        }
567
    }
1✔
568

569
    /**
570
     * Serialize a single NodeProxy.
571
     *
572
     * @param p the node proxy
573
     * @return the serialized result
574
     * @throws SAXException if a SAX error occurs
575
     */
576
    public String serialize(final NodeProxy p) throws SAXException {
577
        final StringWriter out = new StringWriter();
×
578
        serialize(p, out);
×
579
        return out.toString();
×
580
    }
581

582
    public void serialize(final NodeProxy p, final Writer out) throws SAXException {
583
        serialize(p, out, true);
×
584
    }
×
585

586
    public void serialize(final NodeProxy p, final Writer out, final boolean prepareStylesheet) throws SAXException {
587
        if (prepareStylesheet) {
×
588
            try {
589
                prepareStylesheets(p.getOwnerDocument());
×
590
            } catch (final TransformerConfigurationException e) {
×
591
                throw new SAXException(e.getMessage(), e);
×
592
            }
593
        }
594

595
        final SAXSerializer prettyPrinter;
596
        if (templates != null) {
×
597
            applyXSLHandler(out);
×
598
            prettyPrinter = null;
×
599
        } else {
×
600
            prettyPrinter = setPrettyPrinter(out, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")), p, false);
×
601
        }
602

603
        try {
604
            serializeToReceiver(p, false);
×
605
        } finally {
×
606
            if (prettyPrinter != null) {
×
607
                releasePrettyPrinter(prettyPrinter);
×
608
            }
609
        }
610
    }
×
611

612
    private void prepareStylesheets(final Document doc) throws TransformerConfigurationException {
613
        if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no"))) {
1✔
614
            @Nullable final String stylesheet = hasXSLPi(doc);
1✔
615
            if (stylesheet != null) {
1✔
616
                setStylesheet(doc, stylesheet);
1✔
617
            }
618
        }
619
        setStylesheetFromProperties(doc);
1✔
620
    }
1✔
621

622
    /**
623
     * Set the ContentHandler to be used during serialization.
624
     *
625
     * @param contentHandler the content handler
626
     * @param lexicalHandler the lexical handle
627
     */
628
    public void setSAXHandlers(final ContentHandler contentHandler, final LexicalHandler lexicalHandler) {
629
        final ReceiverToSAX toSAX = new ReceiverToSAX(contentHandler);
1✔
630
        toSAX.setLexicalHandler(lexicalHandler);
1✔
631
        if ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
1✔
632
            xinclude.setReceiver(toSAX);
1✔
633
            receiver = xinclude;
1✔
634
        } else {
1✔
635
            receiver = toSAX;
1✔
636
        }
637
    }
1✔
638

639
    public void setReceiver(final Receiver receiver) {
640
        this.receiver = receiver;
1✔
641
    }
1✔
642

643
    public void setReceiver(final Receiver receiver, final boolean handleIncludes) {
644
        if (handleIncludes && "yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
1!
645
            xinclude.setReceiver(receiver);
1✔
646
            this.receiver = xinclude;
1✔
647
        } else {
1✔
648
            this.receiver = receiver;
×
649
        }
650
    }
1✔
651

652
    public XIncludeFilter getXIncludeFilter() {
653
        return xinclude;
1✔
654
    }
655

656
    @Override
657
    public void setContentHandler(final ContentHandler handler) {
658
        setSAXHandlers(handler, null);
×
659
    }
×
660

661
    @Override
662
    public @Nullable ContentHandler getContentHandler() {
663
        return null;
×
664
    }
665

666
    /**
667
     * Sets the entityResolver attribute of the Serializer object
668
     *
669
     * @param entityResolver The new entityResolver value
670
     */
671
    public void setEntityResolver(@Nullable final EntityResolver entityResolver) {
672
        this.entityResolver = entityResolver;
×
673
    }
×
674

675
    /**
676
     * Sets the errorHandler attribute of the Serializer object
677
     *
678
     * @param errorHandler The new errorHandler value
679
     */
680
    public void setErrorHandler(@Nullable final ErrorHandler errorHandler) {
681
        this.errorHandler = errorHandler;
×
682
    }
×
683

684
    /**
685
     * Sets the feature attribute of the Serializer object
686
     *
687
     * @param name  The new feature name
688
     * @param value The new feature value
689
     * @throws SAXNotRecognizedException Description of the Exception
690
     * @throws SAXNotSupportedException  Description of the Exception
691
     */
692
    public void setFeature(final String name, final boolean value)
693
            throws SAXNotRecognizedException, SAXNotSupportedException {
694
        if (name.equals(Namespaces.SAX_NAMESPACES) || name.equals(Namespaces.SAX_NAMESPACES_PREFIXES)) {
×
695
            throw new SAXNotSupportedException(name);
×
696
        }
697
        throw new SAXNotRecognizedException(name);
×
698
    }
699

700
    protected SAXSerializer setPrettyPrinter(final Writer writer, final boolean xmlDecl, final NodeProxy root, final boolean applyFilters) {
701
        outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, xmlDecl ? "no" : "yes");
1✔
702

703
        final SAXSerializer xmlout = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
1✔
704
        xmlout.setOutput(writer, outputProperties);
1✔
705

706
        if ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
1✔
707
            xinclude.setReceiver(xmlout);
1✔
708
            receiver = xinclude;
1✔
709
        } else {
1✔
710
            receiver = xmlout;
1✔
711
        }
712

713
        if (root != null && getHighlightingMode() != TAG_NONE) {
1!
714
            final IndexController controller = broker.getIndexController();
×
715
            MatchListener listener = controller.getMatchListener(root);
×
716
            if (listener != null) {
×
717
                final MatchListener last = (MatchListener) listener.getLastInChain();
×
718
                last.setNextInChain(receiver);
×
719
                receiver = listener;
×
720
            }
721
        }
722

723
        if (root == null && applyFilters && customMatchListeners.getFirst() != null) {
1!
724
            customMatchListeners.getLast().setNextInChain(receiver);
×
725
            receiver = customMatchListeners.getFirst();
×
726
        }
727

728
        return xmlout;
1✔
729
    }
730

731
    protected Receiver setupMatchListeners(final NodeProxy p) {
732
        final Receiver oldReceiver = receiver;
1✔
733
        if (getHighlightingMode() != TAG_NONE) {
1!
734
            final IndexController controller = broker.getIndexController();
×
735
            MatchListener listener = controller.getMatchListener(p);
×
736
            if (listener != null) {
×
737
                final MatchListener last = (MatchListener) listener.getLastInChain();
×
738
                last.setNextInChain(receiver);
×
739
                receiver = listener;
×
740
            }
741
        }
742
        return oldReceiver;
1✔
743
    }
744

745
    protected void releasePrettyPrinter(final SAXSerializer prettyPrinter) {
746
        SerializerPool.getInstance().returnObject(prettyPrinter);
1✔
747
    }
1✔
748

749
    protected void setStylesheetFromProperties(final Document doc) throws TransformerConfigurationException {
750
        if (templates != null) {
1✔
751
            return;
1✔
752
        }
753

754
        final String stylesheet = outputProperties.getProperty(EXistOutputKeys.STYLESHEET);
1✔
755
        if (stylesheet != null) {
1✔
756
            if (doc instanceof DocumentImpl) {
1!
757
                setStylesheet(doc, stylesheet);
1✔
758
            } else {
1✔
759
                setStylesheet(null, stylesheet);
×
760
            }
761
        }
762
    }
1✔
763

764
    protected void checkStylesheetParams() {
765
        if (xslHandler == null) {
1!
766
            return;
×
767
        }
768

769
        for (final Enumeration<?> e = outputProperties.propertyNames(); e.hasMoreElements(); ) {
1✔
770
            String property = (String) e.nextElement();
1✔
771
            if (property.startsWith(EXistOutputKeys.STYLESHEET_PARAM)) {
1✔
772
                final String value = outputProperties.getProperty(property);
1✔
773
                property = property.substring(EXistOutputKeys.STYLESHEET_PARAM.length() + 1);
1✔
774
                xslHandler.getTransformer().setParameter(property, value);
1✔
775
            }
776
        }
777
    }
1✔
778

779
    /**
780
     * Plug an XSL stylesheet into the processing pipeline.
781
     * All output will be passed to this stylesheet.
782
     *
783
     * @param doc        the document
784
     * @param stylesheet the stylesheet
785
     * @throws TransformerConfigurationException if the stylesheet cannot be set
786
     */
787
    public void setStylesheet(final Document doc, final @Nullable String stylesheet) throws TransformerConfigurationException {
788
        if (stylesheet == null) {
1!
789
            templates = null;
×
790
            return;
×
791
        }
792

793
        final long start = System.currentTimeMillis();
1✔
794
        xslHandler = null;
1✔
795
        @Nullable XmldbURI stylesheetUri = null;
1✔
796
        @Nullable URI externalUri = null;
1✔
797
        try {
798
            stylesheetUri = XmldbURI.xmldbUriFor(stylesheet);
1✔
799
            if (!stylesheetUri.toCollectionPathURI().equals(stylesheetUri)) {
1✔
800
                externalUri = stylesheetUri.getXmldbURI();
1✔
801
            }
802
        } catch (final URISyntaxException e) {
1✔
803
            //could be an external URI!
804
            try {
805
                externalUri = new URI(stylesheet);
×
806
            } catch (final URISyntaxException ee) {
×
807
                throw new IllegalArgumentException("Stylesheet URI could not be parsed: " + ee.getMessage());
×
808
            }
809
        }
810
        // does stylesheet point to an external resource?
811
        if (externalUri != null) {
1✔
812
            final StreamSource source = new StreamSource(externalUri.toString());
1✔
813
            this.templates = factory.get().newTemplates(source);
1✔
814
            // read stylesheet from the database
815
        } else {
1✔
816
            // if stylesheet is relative, add path to the
817
            // current collection and normalize
818
            if (doc != null && doc instanceof DocumentImpl) {
1!
819
                stylesheetUri = ((DocumentImpl) doc).getCollection().getURI().resolveCollectionPath(stylesheetUri).normalizeCollectionPath();
1✔
820
            }
821

822
            // load stylesheet from eXist
823
            DocumentImpl xsl = null;
1✔
824
            try {
825
                xsl = broker.getResource(stylesheetUri, Permission.READ);
1✔
826
            } catch (final PermissionDeniedException e) {
1✔
827
                throw new TransformerConfigurationException("permission denied to read " + stylesheetUri);
×
828
            }
829
            if (xsl == null) {
1!
830
                throw new TransformerConfigurationException("stylesheet not found: " + stylesheetUri);
×
831
            }
832

833
            //TODO: use xmldbURI
834
            if (xsl.getCollection() != null) {
1!
835
                factory.get().setURIResolver(new InternalURIResolver(xsl.getCollection().getURI().toString()));
1✔
836
            }
837

838
            // save handlers
839
            Receiver oldReceiver = receiver;
1✔
840

841
            // compile stylesheet
842
            factory.get().setErrorListener(new ErrorListener());
1✔
843
            final TemplatesHandler handler = factory.get().newTemplatesHandler();
1✔
844
            receiver = new ReceiverToSAX(handler);
1✔
845
            try {
846
                this.serializeToReceiver(xsl, true);
1✔
847
                templates = handler.getTemplates();
1✔
848
            } catch (final SAXException e) {
1✔
849
                throw new TransformerConfigurationException(e.getMessage(), e);
×
850
            }
851

852
            // restore handlers
853
            receiver = oldReceiver;
1✔
854
            factory.get().setURIResolver(null);
1✔
855
        }
856
        LOG.debug("compiling stylesheet took {}", System.currentTimeMillis() - start);
1✔
857
        if (templates != null) {
1!
858
            xslHandler = factory.get().newTransformerHandler(templates);
1✔
859
            try {
860
                xslHandler.startDocument();
1✔
861
                documentStarted = true;
1✔
862
            } catch (final SAXException e) {
1✔
863
                throw new TransformerConfigurationException(e.getMessage(), e);
×
864
            }
865
        }
866
//                        xslHandler.getTransformer().setOutputProperties(outputProperties);
867
        checkStylesheetParams();
1✔
868
    }
1✔
869

870
    /**
871
     * Set stylesheet parameter
872
     *
873
     * @param name  the parameter name
874
     * @param value the parameter value
875
     */
876
    public void setStylesheetParam(final String name, final String value) {
877
        if (xslHandler != null) {
×
878
            xslHandler.getTransformer().setParameter(name, value);
×
879
        }
880
    }
×
881

882
    protected void setXSLHandler(@Nullable final NodeProxy root, final boolean applyFilters) {
883
        if (templates != null && xslHandler != null) {
1!
884
            final SAXResult result = new SAXResult();
1✔
885
            final boolean processXInclude =
1✔
886
                    "yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"));
1✔
887

888
            final ReceiverToSAX filter;
889
            if (processXInclude) {
1!
890
                final Receiver xincludeReceiver = xinclude.getReceiver();
1✔
891
                if (xincludeReceiver != null && xincludeReceiver instanceof SAXSerializer) {
1!
892
                    filter = new ReceiverToSAX((SAXSerializer) xincludeReceiver);
×
893
                } else {
×
894
                    filter = (ReceiverToSAX) xincludeReceiver;
1✔
895
                }
896
            } else {
1✔
897
                filter = (ReceiverToSAX) receiver;
×
898
            }
899

900
            result.setHandler(filter.getContentHandler());
1✔
901
            result.setLexicalHandler(filter.getLexicalHandler());
1✔
902

903
            filter.setLexicalHandler(xslHandler);
1✔
904
            filter.setContentHandler(xslHandler);
1✔
905

906
            xslHandler.setResult(result);
1✔
907
            if (processXInclude) {
1!
908
                xinclude.setReceiver(new ReceiverToSAX(xslHandler));
1✔
909
                receiver = xinclude;
1✔
910
            } else {
1✔
911
                receiver = new ReceiverToSAX(xslHandler);
×
912
            }
913
        }
914
        if (root != null && getHighlightingMode() != TAG_NONE) {
1✔
915
            final IndexController controller = broker.getIndexController();
1✔
916
            MatchListener listener = controller.getMatchListener(root);
1✔
917
            if (listener != null) {
1!
918
                final MatchListener last = (MatchListener) listener.getLastInChain();
×
919
                last.setNextInChain(receiver);
×
920
                receiver = listener;
×
921
            }
922
        }
923
        if (applyFilters && root == null && customMatchListeners.getFirst() != null) {
1!
924
            customMatchListeners.getLast().setNextInChain(receiver);
×
925
            receiver = customMatchListeners.getFirst();
×
926
        }
927
    }
1✔
928

929
    public void toSAX(final DocumentImpl doc) throws SAXException {
930
        try {
931
            prepareStylesheets(doc);
1✔
932
        } catch (final TransformerConfigurationException e) {
1✔
933
            throw new SAXException(e.getMessage(), e);
×
934
        }
935
        setXSLHandler(null, true);
1✔
936
        serializeToReceiver(doc, "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
1✔
937
    }
1✔
938

939
    public void toSAX(final NodeValue n) throws SAXException {
940
        try {
941
            if (n.getType() == Type.DOCUMENT && !(n instanceof NodeProxy)) {
1✔
942
                setStylesheetFromProperties((Document) n);
1✔
943
            } else {
1✔
944
                setStylesheetFromProperties(n.getOwnerDocument());
1✔
945
            }
946
        } catch (final TransformerConfigurationException e) {
1✔
947
            throw new SAXException(e.getMessage(), e);
×
948
        }
949
        setXSLHandler(n.getImplementationType() == NodeValue.PERSISTENT_NODE ? (NodeProxy) n : null, false);
1✔
950
        serializeToReceiver(n, "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
1✔
951
    }
1✔
952

953
    public void toSAX(final NodeProxy p) throws SAXException {
954
        try {
955
            setStylesheetFromProperties(p.getOwnerDocument());
1✔
956
        } catch (final TransformerConfigurationException e) {
1✔
957
            throw new SAXException(e.getMessage(), e);
×
958
        }
959
        setXSLHandler(p, false);
1✔
960
        if (p.getNodeId() == NodeId.DOCUMENT_NODE) {
1✔
961
            serializeToReceiver(p.getOwnerDocument(), "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
1✔
962
        } else {
1✔
963
            serializeToReceiver(p, "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
1✔
964
        }
965
    }
1✔
966

967
    /**
968
     * Serialize the items in the given sequence to SAX, starting with item start. If parameter
969
     * wrap is set to true, output a wrapper element to enclose the serialized items. The
970
     * wrapper element will be in namespace {@link org.exist.Namespaces#EXIST_NS} and has the following form:
971
     * <p>
972
     * &lt;exist:result hits="sequence length" start="value of start" count="value of count"&gt;
973
     *
974
     * @param seq             The sequence to serialize
975
     * @param start           The position in the sequence to start serialization from
976
     * @param count           The number of items from the start position to serialize
977
     * @param wrap            Indicates whether the output should be wrapped
978
     * @param typed           Indicates whether the output types should be wrapped
979
     * @param compilationTime The time taken to compile the query which produced the sequence
980
     * @param executionTime   The time taken to execute the query which produced the sequence
981
     * @throws SAXException If an error occurs during serialization
982
     */
983
    public void toSAX(final Sequence seq, int start, final int count, final boolean wrap, final boolean typed, final long compilationTime, final long executionTime) throws SAXException {
984
        try {
985
            setStylesheetFromProperties(null);
1✔
986
        } catch (final TransformerConfigurationException e) {
1✔
987
            throw new SAXException(e.getMessage(), e);
×
988
        }
989
        setXSLHandler(null, false);
1✔
990
        final AttrList attrs = new AttrList();
1✔
991
        attrs.addAttribute(ATTR_HITS_QNAME, Integer.toString(seq.getItemCount()));
1✔
992
        attrs.addAttribute(ATTR_START_QNAME, Integer.toString(start));
1✔
993
        attrs.addAttribute(ATTR_COUNT_QNAME, Integer.toString(count));
1✔
994
        if (outputProperties.getProperty(PROPERTY_SESSION_ID) != null) {
1!
995
            attrs.addAttribute(ATTR_SESSION_ID, outputProperties.getProperty(PROPERTY_SESSION_ID));
×
996
        }
997
        attrs.addAttribute(ATTR_COMPILATION_TIME_QNAME, Long.toString(compilationTime));
1✔
998
        attrs.addAttribute(ATTR_EXECUTION_TIME_QNAME, Long.toString(compilationTime));
1✔
999

1000
        if (!documentStarted) {
1!
1001
            receiver.startDocument();
1✔
1002
            documentStarted = true;
1✔
1003
        }
1004
        if (wrap) {
1✔
1005
            receiver.startPrefixMapping("exist", Namespaces.EXIST_NS);
1✔
1006
            receiver.startElement(ELEM_RESULT_QNAME, attrs);
1✔
1007
        }
1008

1009
        for (int i = --start; i < start + count; i++) {
1✔
1010
            final Item item = seq.itemAt(i);
1✔
1011
            if (item == null) {
1!
1012
                LOG.debug("item {} not found", i);
×
1013
                continue;
×
1014
            }
1015
            itemToSAX(item, typed, wrap);
1✔
1016
        }
1017

1018
        if (wrap) {
1✔
1019
            receiver.endElement(ELEM_RESULT_QNAME);
1✔
1020
            receiver.endPrefixMapping("exist");
1✔
1021
        }
1022
        receiver.endDocument();
1✔
1023
    }
1✔
1024

1025
    /**
1026
     * Serialize the items in the given sequence to SAX, starting with item start. If parameter
1027
     * wrap is set to true, output a wrapper element to enclose the serialized items. The
1028
     * wrapper element will be in namespace {@link org.exist.Namespaces#EXIST_NS} and has the following form:
1029
     * <p>
1030
     * &lt;exist:result hits="sequence length" start="value of start" count="value of count"&gt;
1031
     *
1032
     * @param seq The sequence to serialize
1033
     * @throws SAXException If an error occurs during serialization
1034
     */
1035
    public void toSAX(final Sequence seq) throws SAXException {
1036
        try {
1037
            setStylesheetFromProperties(null);
×
1038
        } catch (final TransformerConfigurationException e) {
×
1039
            throw new SAXException(e.getMessage(), e);
×
1040
        }
1041

1042
        setXSLHandler(null, false);
×
1043

1044
        if (!documentStarted) {
×
1045
            receiver.startDocument();
×
1046
            documentStarted = true;
×
1047
        }
1048

1049
        try {
1050
            final SequenceIterator itSeq = seq.iterate();
×
1051
            while (itSeq.hasNext()) {
×
1052
                final Item item = itSeq.nextItem();
×
1053
                itemToSAX(item, false, false);
×
1054
            }
1055
        } catch (final XPathException xpe) {
×
1056
            throw new SAXException(xpe.getMessage(), xpe);
×
1057
        }
1058

1059
        receiver.endDocument();
×
1060
    }
×
1061

1062
    /**
1063
     * Serializes an Item
1064
     *
1065
     * @param item  The item to serialize
1066
     * @param wrap  Indicates whether the output should be wrapped
1067
     * @param typed Indicates whether the output types should be wrapped
1068
     * @throws SAXException If an error occurs during serialization
1069
     */
1070
    public void toSAX(final Item item, final boolean wrap, final boolean typed) throws SAXException {
1071
        try {
1072
            setStylesheetFromProperties(null);
1✔
1073
        } catch (final TransformerConfigurationException e) {
1✔
1074
            throw new SAXException(e.getMessage(), e);
×
1075
        }
1076

1077
        setXSLHandler(null, false);
1✔
1078
        final AttrList attrs = new AttrList();
1✔
1079
        attrs.addAttribute(ATTR_HITS_QNAME, "1");
1✔
1080
        attrs.addAttribute(ATTR_START_QNAME, "1");
1✔
1081
        attrs.addAttribute(ATTR_COUNT_QNAME, "1");
1✔
1082
        if (outputProperties.getProperty(PROPERTY_SESSION_ID) != null) {
1!
1083
            attrs.addAttribute(ATTR_SESSION_ID, outputProperties.getProperty(PROPERTY_SESSION_ID));
×
1084
        }
1085

1086
        if (!documentStarted) {
1!
1087
            receiver.startDocument();
1✔
1088
            documentStarted = true;
1✔
1089
        }
1090

1091
        if (wrap) {
1!
1092
            receiver.startPrefixMapping("exist", Namespaces.EXIST_NS);
×
1093
            receiver.startElement(ELEM_RESULT_QNAME, attrs);
×
1094
        }
1095

1096
        itemToSAX(item, typed, wrap);
1✔
1097

1098
        if (wrap) {
1!
1099
            receiver.endElement(ELEM_RESULT_QNAME);
×
1100
            receiver.endPrefixMapping("exist");
×
1101
        }
1102

1103
        receiver.endDocument();
1✔
1104
    }
1✔
1105

1106
    private void itemToSAX(final Item item, final boolean typed, final boolean wrap) throws SAXException {
1107
        if (Type.subTypeOf(item.getType(), Type.NODE)) {
1✔
1108
            final NodeValue node = (NodeValue) item;
1✔
1109

1110
            if (typed) {
1!
1111
                //TODO the typed and wrapped stuff should ideally be replaced
1112
                //with Marshaller.marshallItem
1113
                //unfortrunately calling Marshaller.marshallItem(broker, item, new SAXToReceiver(receiver))
1114
                //results in a stack overflow
1115
                //TODO consider a full XDM serializer in place of this for these special needs
1116

1117
                serializeTypePreNode(node);
×
1118
                if (node.getType() == Type.ATTRIBUTE) {
×
1119
                    serializeTypeAttributeValue(node);
×
1120
                } else {
×
1121
                    serializeToReceiver(node, false);
×
1122
                }
1123
                serializeTypePostNode(node);
×
1124
            } else {
×
1125
                serializeToReceiver(node, false);
1✔
1126
            }
1127
        } else {
1✔
1128
            if (wrap) {
1✔
1129
                final AttrList attrs = new AttrList();
1✔
1130
                attrs.addAttribute(ATTR_TYPE_QNAME, Type.getTypeName(item.getType()));
1✔
1131
                receiver.startElement(ELEM_VALUE_QNAME, attrs);
1✔
1132
            }
1133
            try {
1134
                receiver.characters(item.getStringValue());
1✔
1135
            } catch (final XPathException e) {
1✔
1136
                throw new SAXException(e.getMessage(), e);
×
1137
            }
1138
            if (wrap) {
1✔
1139
                receiver.endElement(ELEM_VALUE_QNAME);
1✔
1140
            }
1141
        }
1142
    }
1✔
1143

1144
    public void toReceiver(final NodeProxy p, final boolean highlightMatches) throws SAXException {
1145
        toReceiver(p, highlightMatches, true);
×
1146
    }
×
1147

1148
    public void toReceiver(final NodeProxy p, final boolean highlightMatches, final boolean checkAttributes) throws SAXException {
1149
        final Receiver oldReceiver = highlightMatches ? setupMatchListeners(p) : receiver;
1✔
1150
        serializeToReceiver(p, false, checkAttributes);
1✔
1151
        receiver = oldReceiver;
1✔
1152
    }
1✔
1153

1154
    protected abstract void serializeToReceiver(NodeProxy p, boolean generateDocEvent, boolean checkAttributes) throws SAXException;
1155

1156
    protected abstract void serializeToReceiver(DocumentImpl doc, boolean generateDocEvent)
1157
            throws SAXException;
1158

1159
    protected void serializeToReceiver(final NodeValue v, final boolean generateDocEvents)
1160
            throws SAXException {
1161
        if (v.getImplementationType() == NodeValue.PERSISTENT_NODE) {
1✔
1162
            serializeToReceiver((NodeProxy) v, generateDocEvents, true);
1✔
1163
        } else {
1✔
1164
            serializeToReceiver((org.exist.dom.memtree.NodeImpl) v, generateDocEvents);
1✔
1165
        }
1166
    }
1✔
1167

1168
    protected void serializeToReceiver(final org.exist.dom.memtree.NodeImpl n, final boolean generateDocEvents)
1169
            throws SAXException {
1170
        if (generateDocEvents && !documentStarted) {
1!
1171
            receiver.startDocument();
1✔
1172
        }
1173
        setDocument(null);
1✔
1174
        if (n.getNodeType() == Node.DOCUMENT_NODE) {
1✔
1175
            final org.exist.dom.memtree.DocumentImpl doc = (org.exist.dom.memtree.DocumentImpl) n;
1✔
1176
            setXQueryContext(doc.getContext());
1✔
1177

1178
            if (doc.getDoctype() != null) {
1✔
1179
                if ("yes".equals(getProperty(EXistOutputKeys.OUTPUT_DOCTYPE, "no"))) {
1✔
1180
                    final DocumentType docType = doc.getDoctype();
1✔
1181
                    receiver.documentType(docType.getName(), docType.getPublicId(), docType.getSystemId());
1✔
1182
                }
1183
            }
1184

1185
            //TODO set XSL //code from set Stlyesheet XSL_PI
1186
            if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no"))) {
1✔
1187
                final String stylesheet = hasXSLPi(doc);
1✔
1188
                if (stylesheet != null) {
1✔
1189
                    try {
1190
                        setStylesheet(doc, stylesheet);
1✔
1191
                    } catch (final TransformerConfigurationException e) {
1✔
1192
                        throw new SAXException(e.getMessage(), e);
×
1193
                    }
1194
                }
1195
            }
1196
            try {
1197
                setStylesheetFromProperties(doc);
1✔
1198
            } catch (final TransformerConfigurationException e) {
1✔
1199
                throw new SAXException(e.getMessage(), e);
×
1200
            }
1201
            setXSLHandler(null, true);
1✔
1202
        } else {
1✔
1203
            setXQueryContext(n.getOwnerDocument().getContext());
1✔
1204
            //TODO unSet XSL code from
1205
            try {
1206
                setStylesheetFromProperties(null);
1✔
1207
            } catch (final TransformerConfigurationException e) {
1✔
1208
                throw new SAXException(e.getMessage(), e);
×
1209
            }
1210
            setXSLHandler(null, false);
1✔
1211
        }
1212
        n.streamTo(this, receiver);
1✔
1213
        if (generateDocEvents) {
1✔
1214
            receiver.endDocument();
1✔
1215
        }
1216
    }
1✔
1217

1218
    @Override
1219
    public void setDTDHandler(final DTDHandler handler) {
1220
    }
×
1221

1222
    @Override
1223
    public @Nullable DTDHandler getDTDHandler() {
1224
        return null;
×
1225
    }
1226

1227
    /**
1228
     * Check if the document has an xml-stylesheet processing instruction
1229
     * that references an XSLT stylesheet. Return the link to the stylesheet.
1230
     *
1231
     * @param doc the document
1232
     * @return link to the stylesheet, or null if there is no xml-stylesheet processing instruction.
1233
     */
1234
    public @Nullable String hasXSLPi(final Document doc) {
1235
        final boolean applyXSLPI = outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no").equalsIgnoreCase("yes");
1✔
1236
        if (!applyXSLPI) {
1!
1237
            return null;
×
1238
        }
1239

1240
        final NodeList docChildren = doc.getChildNodes();
1✔
1241
        for (int i = 0; i < docChildren.getLength(); i++) {
1✔
1242
            final Node node = docChildren.item(i);
1✔
1243
            if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
1✔
1244
                    && "xml-stylesheet".equals(((ProcessingInstruction) node).getTarget())) {
1✔
1245
                // found <?xml-stylesheet?>
1246
                final String xsl = ((ProcessingInstruction) node).getData();
1✔
1247
                final String type = XMLUtil.parseValue(xsl, "type");
1✔
1248
                if (type != null && (type.equals(MimeType.XML_TYPE.getName()) || type.equals(MimeType.XSL_TYPE.getName()) || type.equals(MimeType.XSLT_TYPE.getName()))) {
1!
1249
                    final String href = XMLUtil.parseValue(xsl, "href");
1✔
1250
                    if (href == null) {
1!
1251
                        continue;
×
1252
                    }
1253
                    return href;
1✔
1254
                }
1255
            }
1256
        }
1257
        return null;
1✔
1258
    }
1259

1260

1261
    /**
1262
     * Quick code fix for the remote XQJ API implementation.
1263
     * <p>
1264
     * attribute name { "value" } ---&gt; goes through fine.
1265
     * <p>
1266
     * fn:doc($expr)/element()/attribute() ---&gt; fails, as this is
1267
     * contained within the Database (not an in memory attribute).
1268
     *
1269
     * @param item a NodeValue
1270
     * @throws SAXException if the attribute can't be serialized
1271
     * @author Charles Foster
1272
     */
1273
    protected void serializeTypeAttributeValue(final NodeValue item) throws SAXException {
1274
        try {
1275
            receiver.characters(item.getStringValue());
×
1276
        } catch (final XPathException e) {
×
1277
            LOG.error("XPath error trying to retrieve attribute value. {}", e.getMessage(), e);
×
1278
        }
1279
    }
×
1280

1281
    /**
1282
     * Writes a start element for DOCUMENT, ATTRIBUTE and TEXT nodes.
1283
     * This is required for the XQJ API implementation.
1284
     *
1285
     * @param item a NodeValue which will be wrapped in a element.
1286
     * @throws SAXException if the element can't be serialized
1287
     * @author Charles Foster
1288
     */
1289
    protected void serializeTypePreNode(final NodeValue item) throws SAXException {
1290
        @Nullable AttrList attrs = null;
×
1291

1292
        switch (item.getType()) {
×
1293
            case Type.DOCUMENT:
1294

1295
                final String baseUri = ((Document) item).getBaseURI();
×
1296

1297
                attrs = new AttrList();
×
1298
                if (baseUri != null && baseUri.length() > 0) {
×
1299
                    attrs.addAttribute(ATTR_URI_QNAME, baseUri);
×
1300
                }
1301
                if (((Document) item).getDocumentElement() == null) {
×
1302
                    attrs.addAttribute(ATTR_HAS_ELEMENT_QNAME, "false");
×
1303
                }
1304

1305
                receiver.startElement(ELEM_DOC_QNAME, attrs);
×
1306
                break;
×
1307

1308
            case Type.ATTRIBUTE:
1309
                attrs = new AttrList();
×
1310

1311
                String attributeValue;
1312
                if ((attributeValue = item.getNode().getLocalName()) != null && attributeValue.length() > 0) {
×
1313
                    attrs.addAttribute(ATTR_LOCAL_QNAME, attributeValue);
×
1314
                }
1315
                if ((attributeValue = item.getNode().getNamespaceURI()) != null && attributeValue.length() > 0) {
×
1316
                    attrs.addAttribute(ATTR_TNS_QNAME, attributeValue);
×
1317
                }
1318
                if ((attributeValue = item.getNode().getPrefix()) != null && attributeValue.length() > 0) {
×
1319
                    attrs.addAttribute(ATTR_PREFIX_QNAME, attributeValue);
×
1320
                }
1321

1322
                receiver.startElement(ELEM_ATTR_QNAME, attrs);
×
1323
                break;
×
1324

1325
            case Type.TEXT:
1326
                receiver.startElement(ELEM_TEXT_QNAME, null);
×
1327
                break;
1328

1329
            default:
1330
        }
1331
    }
×
1332

1333
    /**
1334
     * Writes an end element for DOCUMENT, ATTRIBUTE and TEXT nodes.
1335
     * This is required for the XQJ API implementation.
1336
     *
1337
     * @param item the item which will be wrapped in an element.
1338
     * @throws SAXException if the element can't be serialized
1339
     * @author Charles Foster
1340
     */
1341
    protected void serializeTypePostNode(final NodeValue item) throws SAXException {
1342
        switch (item.getType()) {
×
1343
            case Type.DOCUMENT:
1344
                receiver.endElement(ELEM_DOC_QNAME);
×
1345
                break;
×
1346
            case Type.ATTRIBUTE:
1347
                receiver.endElement(ELEM_ATTR_QNAME);
×
1348
                break;
×
1349
            case Type.TEXT:
1350
                receiver.endElement(ELEM_TEXT_QNAME);
×
1351
                break;
1352
            default:
1353
        }
1354
    }
×
1355

1356

1357
    /**
1358
     * URIResolver is called by the XSL transformer to handle <xsl:include>,
1359
     * <xsl:import> ...
1360
     *
1361
     * @author <a href="mailto:meier@ifs.tu-darmstadt.de">Wolfgang Meier</a>
1362
     */
1363
    private class InternalURIResolver implements URIResolver {
1364

1365
        private final String collectionId;
1366

1367
        public InternalURIResolver(final String collection) {
1✔
1368
            collectionId = collection;
1✔
1369
        }
1✔
1370

1371
        @Override
1372
        public Source resolve(String href, final String base) throws TransformerException {
1373
            LOG.debug("resolving stylesheet ref {}", href);
×
1374
            if (href.indexOf(':') != Constants.STRING_NOT_FOUND) {
×
1375
                // href is an URL pointing to an external resource
1376
                return null;
×
1377
            }
1378
            ///TODO : use dedicated function in XmldbURI
1379
            final URI baseURI = URI.create(collectionId + "/");
×
1380
            final URI uri = URI.create(href);
×
1381
            href = baseURI.resolve(uri).toString();
×
1382

1383
            // TODO(AR) ideally we should reuse from DBBroker.borrowSerializer
1384
            final Serializer serializer = new NativeSerializer(broker, broker.getConfiguration());
×
1385

1386
            return new SAXSource(serializer, new InputSource(href));
×
1387
        }
1388
    }
1389

1390
    /**
1391
     * An error listener that just rethrows the exception
1392
     */
1393
    private static class ErrorListener implements javax.xml.transform.ErrorListener {
1394

1395
        @Override
1396
        public void warning(final TransformerException exception) throws TransformerException {
1397
            LOG.warn("Warning while applying stylesheet: {}", exception.getMessage(), exception);
×
1398
        }
×
1399

1400
        @Override
1401
        public void error(final TransformerException exception) throws TransformerException {
1402
            throw exception;
×
1403
        }
1404

1405
        @Override
1406
        public void fatalError(final TransformerException exception) throws TransformerException {
1407
            throw exception;
×
1408
        }
1409
    }
1410
}
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