• 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

64.95
/exist-core/src/main/java/org/exist/xquery/functions/transform/Transform.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.xquery.functions.transform;
50

51
import org.apache.logging.log4j.LogManager;
52
import org.apache.logging.log4j.Logger;
53
import org.exist.dom.QName;
54
import org.exist.dom.memtree.DocumentBuilderReceiver;
55
import org.exist.dom.memtree.MemTreeBuilder;
56
import org.exist.dom.persistent.NodeProxy;
57
import org.exist.http.servlets.ResponseWrapper;
58
import org.exist.numbering.NodeId;
59
import org.exist.storage.serializers.EXistOutputKeys;
60
import org.exist.storage.serializers.Serializer;
61
import org.exist.storage.serializers.XIncludeFilter;
62
import org.exist.util.serializer.Receiver;
63
import org.exist.util.serializer.ReceiverToSAX;
64
import org.exist.xmldb.XmldbURI;
65
import org.exist.xquery.*;
66
import org.exist.xquery.value.*;
67
import org.exist.xslt.Stylesheet;
68
import org.exist.xslt.TemplatesFactory;
69
import org.exist.xslt.TransformerFactoryAllocator;
70
import org.exist.xslt.XSLTErrorsListener;
71
import org.w3c.dom.Document;
72
import org.w3c.dom.Element;
73
import org.w3c.dom.Node;
74

75
import javax.xml.transform.*;
76
import javax.xml.transform.sax.SAXResult;
77
import javax.xml.transform.sax.TransformerHandler;
78
import javax.xml.transform.stream.StreamResult;
79
import java.io.BufferedOutputStream;
80
import java.io.IOException;
81
import java.io.OutputStream;
82
import java.nio.file.Path;
83
import java.nio.file.Paths;
84
import java.util.Optional;
85
import java.util.Properties;
86

87
/**
88
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
89
 */
90
public class Transform extends BasicFunction {
91

92
    public final static FunctionSignature[] signatures = {
1✔
93
            new FunctionSignature(
1✔
94
                    new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
1✔
95
                    "Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
1✔
96
                            "is specified in the second argument. This should either be an URI or a node. If it is an " +
97
                            "URI, it can either point to an external location or to an XSL stored in the db by using the " +
98
                            "'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
99
                            "fragment and not from a complete document. " +
100
                            "Stylesheet parameters " +
101
                            "may be passed in the third argument using an XML fragment with the following structure: " +
102
                            "<parameters><param name=\"param-name1\" value=\"param-value1\"/>" +
103
                            "</parameters>. There are two special parameters named \"exist:stop-on-warn\" and " +
104
                            "\"exist:stop-on-error\". If set to value \"yes\", eXist will generate an XQuery error " +
105
                            "if the XSL processor reports a warning or error.",
106
                    new SequenceType[]{
1✔
107
                            new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_MORE, "The source-document (node tree)"),
1✔
108
                            new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
1✔
109
                            new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters")
1✔
110
                    },
111
                    new FunctionReturnSequenceType(Type.NODE, Cardinality.ZERO_OR_ONE, "the transformed result (node tree)")),
1✔
112
            new FunctionSignature(
1✔
113
                    new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
1✔
114
                    "Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
1✔
115
                            "is specified in the second argument. This should either be an URI or a node. If it is an " +
116
                            "URI, it can either point to an external location or to an XSL stored in the db by using the " +
117
                            "'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
118
                            "fragment and not from a complete document. " +
119
                            "Stylesheet parameters " +
120
                            "may be passed in the third argument using an XML fragment with the following structure: " +
121
                            "<parameters><param name=\"param-name\" value=\"param-value\"/>" +
122
                            "</parameters>. There are two special parameters named \"exist:stop-on-warn\" and " +
123
                            "\"exist:stop-on-error\". If set to value \"yes\", eXist will generate an XQuery error " +
124
                            "if the XSL processor reports a warning or error. " +
125
                            "The fourth argument specifies attributes to be set on the used Java TransformerFactory with the following structure: " +
126
                            "<attributes><attr name=\"attr-name\" value=\"attr-value\"/></attributes>.  " +
127
                            "The fifth argument specifies serialization " +
128
                            "options in the same way as if they " +
129
                            "were passed to \"declare option exist:serialize\" expression. An additional serialization option, " +
130
                            "\"xinclude-path\", is supported, which specifies a base path against which xincludes will be expanded " +
131
                            "(if there are xincludes in the document). A relative path will be relative to the current " +
132
                            "module load path.",
133
                    new SequenceType[]{
1✔
134
                            new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_MORE, "The source-document (node tree)"),
1✔
135
                            new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
1✔
136
                            new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters"),
1✔
137
                            new FunctionParameterSequenceType("attributes", Type.NODE, Cardinality.ZERO_OR_ONE, "Attributes to pass to the transformation factory"),
1✔
138
                            new FunctionParameterSequenceType("serialization-options", Type.STRING, Cardinality.ZERO_OR_ONE, "The serialization options")},
1✔
139
                    new FunctionReturnSequenceType(Type.NODE, Cardinality.ZERO_OR_ONE, "the transformed result (node tree)")),
1✔
140
            new FunctionSignature(
1✔
141
                    new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
1✔
142
                    "Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
1✔
143
                            "as for the transform function. stream-transform can only be used within a servlet context. Instead " +
144
                            "of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
145
                            "It should thus be the last statement in the XQuery.",
146
                    new SequenceType[]{
1✔
147
                            new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_MORE, "The source-document (node tree)"),
1✔
148
                            new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
1✔
149
                            new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters")
1✔
150
                    },
151
                    new SequenceType(Type.EMPTY_SEQUENCE, Cardinality.EMPTY_SEQUENCE)),
1✔
152
            new FunctionSignature(
1✔
153
                    new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
1✔
154
                    "Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
1✔
155
                            "as for the transform function. stream-transform can only be used within a servlet context. Instead " +
156
                            "of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
157
                            "It should thus be the last statement in the XQuery.",
158
                    new SequenceType[]{
1✔
159
                            new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_MORE, "The source-document (node tree)"),
1✔
160
                            new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
1✔
161
                            new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters"),
1✔
162
                            new FunctionParameterSequenceType("attributes", Type.NODE, Cardinality.ZERO_OR_ONE, "Attributes to pass to the transformation factory"),
1✔
163
                            new FunctionParameterSequenceType("serialization-options", Type.STRING, Cardinality.ZERO_OR_ONE, "The serialization options")},
1✔
164
                    new SequenceType(Type.EMPTY_SEQUENCE, Cardinality.EMPTY_SEQUENCE))
1✔
165
    };
166

167
    private static final Logger logger = LogManager.getLogger(Transform.class);
1✔
168

169
    private boolean stopOnError = true;
1✔
170
    private boolean stopOnWarn = false;
1✔
171

172
    public Transform(XQueryContext context, FunctionSignature signature) {
173
        super(context, signature);
1✔
174
    }
1✔
175

176
    /* (non-Javadoc)
177
     * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
178
     */
179
    public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
180

181
        final Properties attributes = new Properties();
1✔
182
        final Properties serializationProps = new Properties();
1✔
183
        final Properties stylesheetParams = new Properties();
1✔
184

185
        // Parameter 1 & 2
186
        final Sequence inputNode = args[0];
1✔
187
        final Item stylesheetItem = args[1].itemAt(0);
1✔
188

189
        // Parse 3rd parameter
190
        final Node options = args[2].isEmpty() ? null : ((NodeValue) args[2].itemAt(0)).getNode();
1✔
191
        if (options != null) {
1✔
192
            stylesheetParams.putAll(parseParameters(options));
1✔
193
        }
194

195
        // Parameter 4 when present
196
        if (getArgumentCount() >= 4) {
1✔
197
            final Sequence attrs = args[3];
1✔
198
            attributes.putAll(extractAttributes(attrs));
1✔
199
        }
200

201
        // Parameter 5 when present
202
        if (getArgumentCount() >= 5) {
1✔
203
            //extract serialization options
204
            final Sequence serOpts = args[4];
1✔
205
            serializationProps.putAll(extractSerializationProperties(serOpts));
1✔
206

207
        } else {
1✔
208
            context.checkOptions(serializationProps);
1✔
209
        }
210

211
        boolean expandXIncludes =
1✔
212
                "yes".equals(serializationProps.getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"));
1✔
213

214

215
        final XSLTErrorsListener<XPathException> errorListener =
1✔
216
            new XSLTErrorsListener<XPathException>(stopOnError, stopOnWarn) {
1✔
217
                @Override
218
                protected void raiseError(final String error, final TransformerException ex) throws XPathException {
219
                    throw new XPathException(Transform.this, error, ex);
×
220
                }
221
            };
222

223
        // Setup handler and error listener
224
        final TransformerHandler handler = createHandler(stylesheetItem, stylesheetParams, attributes, errorListener);
1✔
225

226

227
        if (isCalledAs("transform")) {
1!
228
            //transform:transform()
229

230
            final ValueSequence seq = new ValueSequence();
1✔
231
            context.pushDocumentContext();
1✔
232

233
            try {
234
                final MemTreeBuilder builder = context.getDocumentBuilder();
1✔
235
                final DocumentBuilderReceiver builderReceiver = new DocumentBuilderReceiver(this, builder, true);
1✔
236
                final SAXResult result = new SAXResult(builderReceiver);
1✔
237
                result.setLexicalHandler(builderReceiver);        //preserve comments etc... from xslt output
1✔
238
                handler.setResult(result);
1✔
239
                final Receiver receiver = new ReceiverToSAX(handler);
1✔
240
                final Serializer serializer = context.getBroker().borrowSerializer();
1✔
241
                try {
242
                    serializer.setProperties(serializationProps);
1✔
243
                    serializer.setReceiver(receiver, true);
1✔
244
                    if (expandXIncludes) {
1!
245
                        String xiPath = serializationProps.getProperty(EXistOutputKeys.XINCLUDE_PATH);
1✔
246
                        if (xiPath != null && !xiPath.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
1!
247
                            final Path f = Paths.get(xiPath).normalize();
×
248
                            if (!f.isAbsolute()) {
×
249
                                xiPath = Paths.get(context.getModuleLoadPath(), xiPath).normalize().toAbsolutePath().toString();
×
250
                            }
251
                        } else {
×
252
                            xiPath = context.getModuleLoadPath();
1✔
253
                        }
254
                        serializer.getXIncludeFilter().setModuleLoadPath(xiPath);
1✔
255
                    }
256
                    serializer.toSAX(inputNode, 1, inputNode.getItemCount(), false, false, 0, 0);
1✔
257

258
                } catch (final Exception e) {
1✔
259
                    throw new XPathException(this, "Exception while transforming node: " + e.getMessage(), e);
×
260
                } finally {
261
                    context.getBroker().returnSerializer(serializer);
1✔
262
                }
263

264
                errorListener.checkForErrors();
1✔
265
                Node next = builder.getDocument().getFirstChild();
1✔
266
                while (next != null) {
1✔
267
                    seq.add((NodeValue) next);
1✔
268
                    next = next.getNextSibling();
1✔
269
                }
270

271
                return seq;
1✔
272
            } finally {
273
                context.popDocumentContext();
1✔
274
            }
275

276
        } else {
277
            //transform:stream-transform()
278

279
            final Optional<ResponseWrapper> maybeResponse = Optional.ofNullable(context.getHttpContext())
×
280
                    .map(XQueryContext.HttpContext::getResponse);
×
281

282
            if (!maybeResponse.isPresent()) {
×
283
                throw new XPathException(this, ErrorCodes.XPDY0002, "No response object found in the current XQuery context.");
×
284
            }
285

286
            final ResponseWrapper response =  maybeResponse.get();
×
287
            if (!"org.exist.http.servlets.HttpResponseWrapper".equals(response.getClass().getName())) {
×
288
                throw new XPathException(this, ErrorCodes.XPDY0002, signatures[1] +
×
289
                        " can only be used within the EXistServlet or XQueryServlet");
290
            }
291

292
            //setup the response correctly
293
            final String mediaType = handler.getTransformer().getOutputProperty("media-type");
×
294
            final String encoding = handler.getTransformer().getOutputProperty("encoding");
×
295
            if (mediaType != null) {
×
296
                if (encoding == null) {
×
297
                    response.setContentType(mediaType);
×
298
                } else {
×
299
                    response.setContentType(mediaType + "; charset=" + encoding);
×
300
                }
301
            }
302

303
            //do the transformation
304
            try {
305
                final OutputStream os = new BufferedOutputStream(response.getOutputStream());
×
306
                final StreamResult result = new StreamResult(os);
×
307
                handler.setResult(result);
×
308
                final Serializer serializer = context.getBroker().borrowSerializer();
×
309
                Receiver receiver = new ReceiverToSAX(handler);
×
310

311
                try {
312
                    serializer.setProperties(serializationProps);
×
313
                    if (expandXIncludes) {
×
314
                        XIncludeFilter xinclude = new XIncludeFilter(serializer, receiver);
×
315
                        String xiPath = serializationProps.getProperty(EXistOutputKeys.XINCLUDE_PATH);
×
316
                        if (xiPath != null) {
×
317
                            final Path f = Paths.get(xiPath).normalize();
×
318
                            if (!f.isAbsolute()) {
×
319
                                xiPath = Paths.get(context.getModuleLoadPath(), xiPath).normalize().toAbsolutePath().toString();
×
320
                            }
321

322
                        } else {
×
323
                            xiPath = context.getModuleLoadPath();
×
324
                        }
325

326
                        xinclude.setModuleLoadPath(xiPath);
×
327
                        receiver = xinclude;
×
328
                    }
329
                    serializer.setReceiver(receiver);
×
330
                    serializer.toSAX(inputNode);
×
331

332
                } catch (final Exception e) {
×
333
                    throw new XPathException(this, "Exception while transforming node: " + e.getMessage(), e);
×
334
                } finally {
335
                    context.getBroker().returnSerializer(serializer);
×
336
                }
337

338
                errorListener.checkForErrors();
×
339
                os.close();
×
340

341
                //commit the response
342
                response.flushBuffer();
×
343

344
            } catch (final IOException e) {
×
345
                throw new XPathException(this, "IO exception while transforming node: " + e.getMessage(), e);
×
346
            }
347
            return Sequence.EMPTY_SEQUENCE;
×
348
        }
349
    }
350

351
    /**
352
     * @param stylesheetItem
353
     * @param options
354
     * @param attributes     Attributes to set on the Transformer Factory
355
     * @throws TransformerFactoryConfigurationError
356
     * @throws XPathException
357
     */
358
    private TransformerHandler createHandler(
359
        Item stylesheetItem,
360
        Properties options,
361
        Properties attributes,
362
        XSLTErrorsListener<XPathException> errorListener
363
    )
364
        throws TransformerFactoryConfigurationError, XPathException
365
    {
366

367
        boolean useCache = true;
1✔
368
        final Object property = context.getBroker().getConfiguration().getProperty(TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE);
1✔
369
        if (property != null) {
1!
370
            useCache = (Boolean) property;
1✔
371
        }
372

373
        TransformerHandler handler;
374
        try {
375
            Stylesheet stylesheet = null;
1✔
376
            if (Type.subTypeOf(stylesheetItem.getType(), Type.NODE)) {
1✔
377
                final NodeValue stylesheetNode = (NodeValue) stylesheetItem;
1✔
378
                // if the passed node is a document node or document root element,
379
                // we construct an XMLDB URI and use the caching implementation.
380
                if (stylesheetNode.getImplementationType() == NodeValue.PERSISTENT_NODE) {
1✔
381
                    final NodeProxy root = (NodeProxy) stylesheetNode;
1✔
382
                    if (root.getNodeId() == NodeId.DOCUMENT_NODE || root.getNodeId().getTreeLevel() == 1) {
1!
383

384
                        final String uri = XmldbURI.XMLDB_URI_PREFIX + context.getBroker().getBrokerPool().getId() + "://" + root.getOwnerDocument().getURI();
1✔
385

386
                        stylesheet = TemplatesFactory.stylesheet(uri, context.getModuleLoadPath(), attributes, useCache);
1✔
387
                    }
388
                }
389
                if (stylesheet == null) {
1✔
390
                    stylesheet = TemplatesFactory.stylesheet(
1✔
391
                        getContext().getBroker(),
1✔
392
                        stylesheetNode,
1✔
393
                        context.getModuleLoadPath()
1✔
394
                    );
395
                }
396
            } else {
1✔
397
                String baseUri = context.getModuleLoadPath();
1✔
398
                if (stylesheetItem instanceof Document) {
1!
399
                    baseUri = ((Document) stylesheetItem).getDocumentURI();
×
400

401
                    /*
402
                     * This must be checked because in the event the stylesheet is
403
                     * an in-memory document, it will cause an NPE
404
                     */
405
                    if (baseUri == null) {
×
406
                        baseUri = context.getModuleLoadPath();
×
407
                    } else {
×
408
                        baseUri = baseUri.substring(0, baseUri.lastIndexOf('/'));
×
409
                    }
410
                }
411

412
                final String uri = stylesheetItem.getStringValue();
1✔
413

414
                stylesheet = TemplatesFactory.stylesheet(uri, baseUri, attributes, useCache);
1✔
415
            }
416

417
            handler = stylesheet.newTransformerHandler(getContext().getBroker(), errorListener);
1✔
418

419
            if (options != null) {
1!
420
                setParameters(options, handler.getTransformer());
1✔
421
            }
422

423
        } catch (final Exception e) {
1✔
424
            if (e instanceof XPathException) {
×
425
                throw (XPathException) e;
×
426
            }
427
            throw new XPathException(this, "Unable to set up transformer: " + e.getMessage(), e);
×
428
        }
429
        return handler;
1✔
430
    }
431

432
    private Properties extractSerializationProperties(final Sequence serOpts) throws XPathException {
433
        final Properties serializationProps = new Properties();
1✔
434
        if (!serOpts.isEmpty()) {
1!
435
            final String[] contents = Option.tokenize(serOpts.getStringValue());
1✔
436
            for (String content : contents) {
1✔
437
                final String[] pair = Option.parseKeyValuePair(content);
1✔
438
                if (pair == null) {
1!
439
                    throw new XPathException(this, "Found invalid serialization option: " + content);
×
440
                }
441
                logger.info("Setting serialization property: {} = {}", pair[0], pair[1]);
1✔
442
                serializationProps.setProperty(pair[0], pair[1]);
1✔
443
            }
444
        }
445
        return serializationProps;
1✔
446
    }
447

448
    private Properties extractAttributes(final Sequence attrs) throws XPathException {
449
        if (attrs.isEmpty()) {
1!
450
            return new Properties();
1✔
451
        } else {
452
            return parseElementParam(((NodeValue) attrs.itemAt(0)).getNode(), "attributes", "attr");
×
453
        }
454
    }
455

456
    private Properties parseParameters(final Node options) throws XPathException {
457
        return parseElementParam(options, "parameters", "param");
1✔
458
    }
459

460
    private Properties parseElementParam(final Node elementParam, final String container, final String param) throws XPathException {
461
        final Properties props = new Properties();
1✔
462
        if (elementParam.getNodeType() == Node.ELEMENT_NODE && elementParam.getLocalName().equals(container)) {
1!
463
            Node child = elementParam.getFirstChild();
1✔
464
            while (child != null) {
1✔
465
                if (child.getNodeType() == Node.ELEMENT_NODE && child.getLocalName().equals(param)) {
1!
466
                    final Element elem = (Element) child;
1✔
467
                    final String name = elem.getAttribute("name");
1✔
468
                    final String value = elem.getAttribute("value");
1✔
469
                    if (name.isEmpty() || value.isEmpty()) {
1!
470
                        throw new XPathException(this, "Name or value attribute missing");
×
471
                    }
472

473
                    if ("exist:stop-on-warn".equals(name)) {
1!
474
                        stopOnWarn = "yes".equals(value);
×
475
                    } else if ("exist:stop-on-error".equals(name)) {
1!
476
                        stopOnError = "yes".equals(value);
×
477
                    } else {
×
478
                        props.setProperty(name, value);
1✔
479
                    }
480
                }
481
                child = child.getNextSibling();
1✔
482
            }
483
        }
484
        return props;
1✔
485
    }
486

487
    private void setParameters(Properties parameters, Transformer handler) {
488
        for (Object o : parameters.keySet()) {
1✔
489
            final String key = (String) o;
1✔
490
            handler.setParameter(key, parameters.getProperty(key));
1✔
491
        }
492
    }
1✔
493
}
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