1 /* 2 * Copyright (c) 2012-2022, 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.log.Logger; 33 import java.io.ByteArrayInputStream; 34 import java.io.IOException; 35 import java.nio.charset.StandardCharsets; 36 import javax.xml.parsers.DocumentBuilder; 37 import javax.xml.parsers.DocumentBuilderFactory; 38 import javax.xml.parsers.ParserConfigurationException; 39 import lombok.EqualsAndHashCode; 40 import lombok.ToString; 41 import org.w3c.dom.Document; 42 import org.xml.sax.SAXException; 43 44 /** 45 * Convenient parser of XML to DOM. 46 * 47 * <p>Objects of this class are immutable and thread-safe. 48 * 49 * @since 0.1 50 */ 51 @ToString 52 @EqualsAndHashCode(of = "xml") 53 final class DomParser { 54 55 /** 56 * The XML as a text. 57 */ 58 private final transient byte[] xml; 59 60 /** 61 * Document builder factory to use for parsing. 62 */ 63 private final transient DocumentBuilderFactory factory; 64 65 /** 66 * Public ctor. 67 * 68 * <p>An {@link IllegalArgumentException} may be thrown if the parameter 69 * passed is not in XML format. It doesn't perform a strict validation 70 * and is not guaranteed that an exception will be thrown whenever 71 * the parameter is not XML. 72 * 73 * <p>It is assumed that the text is in UTF-8. 74 * 75 * @param fct Document builder factory to use 76 * @param txt The XML in text (in UTF-8) 77 */ 78 DomParser(final DocumentBuilderFactory fct, final String txt) { 79 this(fct, txt.getBytes(StandardCharsets.UTF_8)); 80 } 81 82 /** 83 * Public ctor. 84 * 85 * <p>An {@link IllegalArgumentException} may be thrown if the parameter 86 * passed is not in XML format. It doesn't perform a strict validation 87 * and is not guaranteed that an exception will be thrown whenever 88 * the parameter is not XML. 89 * 90 * @param fct Document builder factory to use 91 * @param bytes The XML in bytes 92 */ 93 @SuppressWarnings("PMD.ArrayIsStoredDirectly") 94 DomParser(final DocumentBuilderFactory fct, final byte[] bytes) { 95 this.xml = bytes; 96 this.factory = fct; 97 } 98 99 /** 100 * Get document of body. 101 * @return The document 102 */ 103 public Document document() { 104 final DocumentBuilder builder; 105 try { 106 builder = this.factory.newDocumentBuilder(); 107 } catch (final ParserConfigurationException ex) { 108 throw new IllegalArgumentException( 109 String.format( 110 "Failed to create document builder by %s", 111 this.factory.getClass().getName() 112 ), 113 ex 114 ); 115 } 116 final long start = System.nanoTime(); 117 final Document doc; 118 try { 119 doc = builder.parse(new ByteArrayInputStream(this.xml)); 120 } catch (final IOException | SAXException ex) { 121 throw new IllegalArgumentException( 122 String.format( 123 "Can't parse by %s, most probably the XML is invalid", 124 builder.getClass().getName() 125 ), 126 ex 127 ); 128 } 129 if (Logger.isTraceEnabled(this)) { 130 Logger.trace( 131 this, 132 "%s parsed %d bytes of XML in %[nano]s", 133 builder.getClass().getName(), 134 this.xml.length, 135 System.nanoTime() - start 136 ); 137 } 138 return doc; 139 } 140 141 }