1 /* 2 * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package jnlp.converter.parser.xml; 25 26 import javax.xml.parsers.ParserConfigurationException; 27 import javax.xml.parsers.SAXParser; 28 import javax.xml.parsers.SAXParserFactory; 29 30 import org.xml.sax.InputSource; 31 import org.xml.sax.Attributes; 32 import org.xml.sax.SAXException; 33 import org.xml.sax.helpers.DefaultHandler; 34 35 import java.io.StringReader; 36 import java.io.IOException; 37 import java.util.Stack; 38 39 public class XMLParser extends DefaultHandler { 40 41 private XMLNode _root; 42 private final String _source; 43 private Stack<XMLNode> _inProgress; 44 private String _characters; 45 46 // although defined in com.sun.org.apache.xerces.internal.impl.Constants, 47 // we should not be able to access that, so defined here 48 private final static String DTD_DOWNLOAD = 49 "http://apache.org/xml/features/nonvalidating/load-external-dtd"; 50 51 private static final SAXParserFactory SPF = SAXParserFactory.newInstance(); 52 53 /* 54 * Construct an <code>XMLParser</code>. 55 * 56 * @param source - the source text to parse. 57 */ 58 public XMLParser(String source) { 59 _source = source.trim(); 60 } 61 62 public XMLNode parse() throws SAXException { 63 // normally we parse without validating, but leave option to parse 64 // with validation, possibly controlled by config option. 65 return parse(false); 66 } 67 68 public XMLNode parse(boolean validating) throws SAXException { 69 _root = null; 70 _inProgress = new Stack<>(); 71 72 try { 73 InputSource is = new InputSource(new StringReader(_source)); 74 SPF.setValidating(validating); 75 // only download dtd file from DOCTYPE if we are doing validation 76 try { 77 SPF.setFeature(DTD_DOWNLOAD, validating); 78 } catch (Exception e) { 79 } 80 SAXParser sp = SPF.newSAXParser(); 81 sp.parse(is, this); 82 } catch (ParserConfigurationException | IOException pce) { 83 throw new SAXException(pce); 84 } 85 86 return _root; 87 } 88 89 @Override 90 public void startElement(String uri, String localeName, String qName, 91 Attributes attributes) throws SAXException { 92 93 XMLAttribute first = null; 94 XMLAttribute last = null; 95 int len = attributes.getLength(); 96 97 for (int i = 0; i < len; i++) { 98 XMLAttribute att = new XMLAttribute( 99 // in old implementation attribute names and values were trimmed 100 attributes.getQName(i).trim(), attributes.getValue(i).trim()); 101 if (first == null) { 102 first = att; 103 } 104 if (last != null) { 105 last.setNext(att); 106 } 107 last = att; 108 } 109 _inProgress.push(new XMLNode(qName, first)); 110 _characters = null; 111 } 112 113 @Override 114 public void endElement(String uri, String localeName, String elementName) 115 throws SAXException { 116 XMLNode node = _inProgress.pop(); 117 // <information> 118 // <title>Title</title> 119 // <vendor>Vendor</vendor> "Some whitespaces" 120 // </information> 121 // In example above when we receive end of <information> we will 122 // have _characters set to whitespace and new line and it will be 123 // added as child node to <information>. This will break our cache code 124 // which will think that JNLP file changed on server even if it is not 125 // and thus we might not load properly. 126 // 127 // <application-desc name="HelloWorld" main-class="HelloWorld"> 128 // <argument> test with whitespaces </argument> 129 // </application-desc> 130 // From example above we want to include whitespaces for <argument>. 131 // 132 // <node1> 133 // <node2>abc</node2> 134 // xyz (might be whitespaces) 135 // </node1> 136 // In JNLP spec we do not have cases when node have nested nodes as 137 // well as text which is whitespaces only. 138 // 139 // So to fix it lets check if ending node have nested nodes, then do 140 // not add whitespaces only node. 141 if (node != null && node.getNested() != null && _characters != null) { 142 String trimCharacters = _characters.trim(); 143 if ((trimCharacters == null) || (trimCharacters.length() == 0)) { 144 _characters = null; // No need to add whitespaces only 145 } 146 } 147 if ((_characters != null) && (_characters.trim().length() > 0)) { 148 addChild(node, new XMLNode(_characters)); 149 } 150 151 if (_inProgress.isEmpty()) { 152 _root = node; 153 } else { 154 addChild(_inProgress.peek(), node); 155 } 156 _characters = null; 157 } 158 159 @Override 160 public void ignorableWhitespace(char[] chars, int start, int length) 161 throws SAXException { 162 String s = new String(chars, start, length); 163 _characters = ((_characters == null) ? s : _characters + s); 164 } 165 166 private void addChild(XMLNode parent, XMLNode child) { 167 child.setParent(parent); 168 169 XMLNode sibling = parent.getNested(); 170 if (sibling == null) { 171 parent.setNested(child); // set us as only child 172 } else { 173 while (sibling.getNext() != null) { 174 sibling = sibling.getNext(); 175 } 176 sibling.setNext(child); // sets us as youngest child 177 } 178 } 179 }