Coverage Report - com.jcabi.xml.XSLDocument
 
Classes in this File Line Coverage Branch Coverage Complexity
XSLDocument
66%
38/57
14%
2/14
1.722
XSLDocument$1
14%
1/7
N/A
1.722
 
 1  
 /**
 2  
  * Copyright (c) 2012-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.xml;
 31  
 
 32  
 import com.jcabi.immutable.ArrayMap;
 33  
 import com.jcabi.log.Logger;
 34  
 import java.io.ByteArrayOutputStream;
 35  
 import java.io.IOException;
 36  
 import java.io.InputStream;
 37  
 import java.io.StringReader;
 38  
 import java.io.UnsupportedEncodingException;
 39  
 import java.net.URI;
 40  
 import java.net.URL;
 41  
 import java.util.Map;
 42  
 import javax.xml.parsers.DocumentBuilderFactory;
 43  
 import javax.xml.parsers.ParserConfigurationException;
 44  
 import javax.xml.transform.ErrorListener;
 45  
 import javax.xml.transform.Result;
 46  
 import javax.xml.transform.Transformer;
 47  
 import javax.xml.transform.TransformerConfigurationException;
 48  
 import javax.xml.transform.TransformerException;
 49  
 import javax.xml.transform.TransformerFactory;
 50  
 import javax.xml.transform.dom.DOMResult;
 51  
 import javax.xml.transform.dom.DOMSource;
 52  
 import javax.xml.transform.stream.StreamResult;
 53  
 import javax.xml.transform.stream.StreamSource;
 54  
 import lombok.EqualsAndHashCode;
 55  
 import org.w3c.dom.Document;
 56  
 
 57  
 /**
 58  
  * Implementation of {@link XSL}.
 59  
  *
 60  
  * <p>Objects of this class are immutable and thread-safe.
 61  
  *
 62  
  * @author Yegor Bugayenko (yegor@teamed.io)
 63  
  * @version $Id: cb82bb5ba9ddc507258666bb58703cbe7cabd641 $
 64  
  * @since 0.4
 65  
  * @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
 66  
  */
 67  0
 @EqualsAndHashCode(of = "xsl")
 68  
 @SuppressWarnings("PMD.TooManyMethods")
 69  
 public final class XSLDocument implements XSL {
 70  
 
 71  
     /**
 72  
      * Strips spaces of whitespace-only text nodes.
 73  
      *
 74  
      * <p>This will NOT remove
 75  
      * existing indentation between Element nodes currently introduced by the
 76  
      * constructor of {@link com.jcabi.xml.XMLDocument}. For example:
 77  
      *
 78  
      * <pre>
 79  
      * {@code
 80  
      * &lt;a&gt;
 81  
      *           &lt;b> TXT &lt;/b>
 82  
      *    &lt;/a>}
 83  
      * </pre>
 84  
      *
 85  
      * becomes
 86  
      *
 87  
      * <pre>
 88  
      * {@code
 89  
      * &lt;a>
 90  
      *     &lt;b> TXT &lt;/b>
 91  
      * &lt;/a>}
 92  
      * </pre>
 93  
      *
 94  
      * @since 0.14
 95  
      */
 96  1
     public static final XSL STRIP = XSLDocument.make(
 97  
         XSL.class.getResourceAsStream("strip.xsl")
 98  
     );
 99  
 
 100  
     /**
 101  
      * DOM document builder factory.
 102  
      */
 103  1
     private static final DocumentBuilderFactory DFACTORY =
 104  
         DocumentBuilderFactory.newInstance();
 105  
 
 106  
     /**
 107  
      * Error listener.
 108  
      */
 109  1
     private static final ErrorListener ERRORS = new ErrorListener() {
 110  
         @Override
 111  
         public void warning(final TransformerException exception) {
 112  0
             Logger.warn(this, exception.getMessageAndLocation());
 113  0
         }
 114  
         @Override
 115  
         public void error(final TransformerException exception) {
 116  0
             Logger.error(this, exception.getMessageAndLocation());
 117  0
         }
 118  
         @Override
 119  
         public void fatalError(final TransformerException exception) {
 120  0
             this.error(exception);
 121  0
         }
 122  
     };
 123  
 
 124  
     /**
 125  
      * XSL document.
 126  
      */
 127  
     private final transient String xsl;
 128  
 
 129  
     /**
 130  
      * Sources.
 131  
      */
 132  
     private final transient Sources sources;
 133  
 
 134  
     /**
 135  
      * Parameters.
 136  
      */
 137  
     private final transient ArrayMap<String, Object> params;
 138  
 
 139  
     /**
 140  
      * Public ctor, from XML as a source.
 141  
      * @param src XSL document body
 142  
      */
 143  
     public XSLDocument(final XML src) {
 144  0
         this(src.toString());
 145  0
     }
 146  
 
 147  
     /**
 148  
      * Public ctor, from URL.
 149  
      * @param url Location of document
 150  
      * @throws IOException If fails to read
 151  
      * @since 0.7.4
 152  
      */
 153  
     public XSLDocument(final URL url) throws IOException {
 154  0
         this(new TextResource(url).toString());
 155  0
     }
 156  
 
 157  
     /**
 158  
      * Public ctor, from URI.
 159  
      * @param uri Location of document
 160  
      * @throws IOException If fails to read
 161  
      * @since 0.15
 162  
      */
 163  
     public XSLDocument(final URI uri) throws IOException {
 164  0
         this(new TextResource(uri).toString());
 165  0
     }
 166  
 
 167  
     /**
 168  
      * Public ctor, from XSL as an input stream.
 169  
      * @param stream XSL input stream
 170  
      */
 171  
     public XSLDocument(final InputStream stream) {
 172  2
         this(new TextResource(stream).toString());
 173  2
     }
 174  
 
 175  
     /**
 176  
      * Public ctor, from XSL as a string.
 177  
      * @param src XML document body
 178  
      */
 179  
     public XSLDocument(final String src) {
 180  9
         this(src, Sources.DUMMY);
 181  9
     }
 182  
 
 183  
     /**
 184  
      * Public ctor, from XSL as a string.
 185  
      * @param src XML document body
 186  
      * @param srcs Sources
 187  
      * @since 0.9
 188  
      */
 189  
     public XSLDocument(final String src, final Sources srcs) {
 190  9
         this(src, srcs, new ArrayMap<String, Object>());
 191  9
     }
 192  
 
 193  
     /**
 194  
      * Public ctor, from XSL as a string.
 195  
      * @param src XML document body
 196  
      * @param srcs Sources
 197  
      * @param map Map of XSL params
 198  
      * @since 0.16
 199  
      */
 200  
     public XSLDocument(final String src, final Sources srcs,
 201  12
         final Map<String, Object> map) {
 202  12
         this.xsl = src;
 203  12
         this.sources = srcs;
 204  12
         this.params = new ArrayMap<String, Object>(map);
 205  12
     }
 206  
 
 207  
     @Override
 208  
     public XSL with(final Sources src) {
 209  1
         return new XSLDocument(this.xsl, src, this.params);
 210  
     }
 211  
 
 212  
     @Override
 213  
     public XSL with(final String name, final Object value) {
 214  2
         return new XSLDocument(
 215  
             this.xsl, this.sources, this.params.with(name, value)
 216  
         );
 217  
     }
 218  
 
 219  
     /**
 220  
      * Make an instance of XSL stylesheet without I/O exceptions.
 221  
      *
 222  
      * <p>This factory method is useful when you need to create
 223  
      * an instance of XSL stylesheet as a static final variable. In this
 224  
      * case you can't catch an exception but this method can help, for example:
 225  
      *
 226  
      * <pre> class Foo {
 227  
      *   private static final XSL STYLESHEET = XSLDocument.make(
 228  
      *     Foo.class.getResourceAsStream("my-stylesheet.xsl")
 229  
      *   );
 230  
      * }</pre>
 231  
      *
 232  
      * @param stream Input stream
 233  
      * @return XSL stylesheet
 234  
      */
 235  
     public static XSL make(final InputStream stream) {
 236  1
         return new XSLDocument(stream);
 237  
     }
 238  
 
 239  
     /**
 240  
      * Make an instance of XSL stylesheet without I/O exceptions.
 241  
      * @param url URL with content
 242  
      * @return XSL stylesheet
 243  
      * @see #make(InputStream)
 244  
      * @since 0.7.4
 245  
      */
 246  
     public static XSL make(final URL url) {
 247  
         try {
 248  0
             return new XSLDocument(url);
 249  0
         } catch (final IOException ex) {
 250  0
             throw new IllegalStateException(ex);
 251  
         }
 252  
     }
 253  
 
 254  
     @Override
 255  
     public String toString() {
 256  0
         return new XMLDocument(this.xsl).toString();
 257  
     }
 258  
 
 259  
     @Override
 260  
     public XML transform(final XML xml) {
 261  
         final Document target;
 262  
         try {
 263  56
             target = XSLDocument.DFACTORY.newDocumentBuilder()
 264  
                 .newDocument();
 265  56
             this.transformInto(
 266  
                 xml, new DOMResult(target)
 267  
             );
 268  0
         } catch (final ParserConfigurationException ex) {
 269  0
             throw new IllegalStateException(ex);
 270  56
         }
 271  56
         return new XMLDocument(target);
 272  
     }
 273  
 
 274  
     @Override
 275  
     public String applyTo(final XML xml) {
 276  3
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 277  3
         this.transformInto(xml, new StreamResult(baos));
 278  
         try {
 279  3
             return baos.toString("UTF-8");
 280  0
         } catch (final UnsupportedEncodingException ex) {
 281  0
             throw new IllegalStateException(ex);
 282  
         }
 283  
     }
 284  
 
 285  
     /**
 286  
      * Tranform XML into result.
 287  
      * @param xml XML
 288  
      * @param result Result
 289  
      * @since 0.11
 290  
      */
 291  
     private void transformInto(final XML xml, final Result result) {
 292  
         final Transformer trans;
 293  59
         synchronized (XSLDocument.class) {
 294  59
             final TransformerFactory factory =
 295  
                 TransformerFactory.newInstance();
 296  
             try {
 297  59
                 factory.setErrorListener(XSLDocument.ERRORS);
 298  59
                 factory.setURIResolver(this.sources);
 299  59
                 trans = factory.newTransformer(
 300  
                     new StreamSource(new StringReader(this.xsl))
 301  
                 );
 302  59
                 trans.setURIResolver(this.sources);
 303  
                 for (final Map.Entry<String, Object> ent
 304  59
                     : this.params.entrySet()) {
 305  2
                     trans.setParameter(ent.getKey(), ent.getValue());
 306  2
                 }
 307  59
                 trans.transform(new DOMSource(xml.node()), result);
 308  0
             } catch (final TransformerConfigurationException ex) {
 309  0
                 throw new IllegalStateException(
 310  
                     String.format(
 311  
                         "failed to configure transformer by %s",
 312  
                         factory.getClass().getName()
 313  
                     ),
 314  
                     ex
 315  
                 );
 316  0
             } catch (final TransformerException ex) {
 317  0
                 throw new IllegalStateException(
 318  
                     String.format(
 319  
                         "failed to transform by %s",
 320  
                         factory.getClass().getName()
 321  
                     ),
 322  
                     ex
 323  
                 );
 324  59
             }
 325  59
         }
 326  59
         Logger.debug(this, "%s transformed XML", trans.getClass().getName());
 327  59
     }
 328  
 
 329  
 }