1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.layoutopt.uix.xml; 18 19 import com.sun.org.apache.xerces.internal.parsers.DOMParser; 20 import com.sun.org.apache.xerces.internal.xni.XMLLocator; 21 import com.sun.org.apache.xerces.internal.xni.NamespaceContext; 22 import com.sun.org.apache.xerces.internal.xni.Augmentations; 23 import com.sun.org.apache.xerces.internal.xni.XNIException; 24 import com.sun.org.apache.xerces.internal.xni.QName; 25 import com.sun.org.apache.xerces.internal.xni.XMLAttributes; 26 27 import javax.xml.parsers.DocumentBuilder; 28 import javax.xml.parsers.DocumentBuilderFactory; 29 import javax.xml.parsers.ParserConfigurationException; 30 31 import org.w3c.dom.Node; 32 import org.w3c.dom.Document; 33 import org.xml.sax.SAXNotRecognizedException; 34 import org.xml.sax.SAXNotSupportedException; 35 import org.xml.sax.SAXException; 36 import org.xml.sax.InputSource; 37 38 import java.io.InputStream; 39 import java.io.IOException; 40 import java.io.File; 41 import java.io.FileInputStream; 42 import java.util.LinkedList; 43 44 /** 45 * Parses XML documents. This class tries to add meta-data in the resulting DOM 46 * trees to indicate the start and end line numbers of each node. 47 */ 48 public class XmlDocumentBuilder { 49 /** 50 * Name of the node user data containing the start line number of the node. 51 * 52 * @see Node#getUserData(String) 53 */ 54 public static final String NODE_START_LINE = "startLine"; 55 56 /** 57 * Name of the node user data containing the end line number of the node. 58 * 59 * @see Node#getUserData(String) 60 */ 61 public static final String NODE_END_LINE = "endLine"; 62 63 private final DocumentBuilder mBuilder; 64 private boolean mHasLineNumbersSupport; 65 66 /** 67 * Creates a new XML document builder. 68 */ XmlDocumentBuilder()69 public XmlDocumentBuilder() { 70 try { 71 Class.forName("com.sun.org.apache.xerces.internal.parsers.DOMParser"); 72 mHasLineNumbersSupport = true; 73 } catch (ClassNotFoundException e) { 74 // Ignore 75 } 76 77 if (!mHasLineNumbersSupport) { 78 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 79 try { 80 mBuilder = factory.newDocumentBuilder(); 81 } catch (ParserConfigurationException e) { 82 throw new IllegalStateException("Could not initialize the XML parser"); 83 } 84 } else { 85 mBuilder = null; 86 } 87 } 88 89 /** 90 * Indicates whether the XML documents created by this class are annotated 91 * with line numbers. 92 * 93 * @return True if the parsed documents contain line numbers meta-data, 94 * false otherwise. 95 * 96 * @see #NODE_START_LINE 97 * @see #NODE_END_LINE 98 */ isHasLineNumbersSupport()99 public boolean isHasLineNumbersSupport() { 100 return mHasLineNumbersSupport; 101 } 102 parse(InputStream inputStream)103 public Document parse(InputStream inputStream) throws SAXException, IOException { 104 if (!mHasLineNumbersSupport) { 105 return mBuilder.parse(inputStream); 106 } else { 107 DOMParser parser = new LineNumberDOMParser(); 108 parser.parse(new InputSource(inputStream)); 109 return parser.getDocument(); 110 } 111 } 112 parse(String content)113 public Document parse(String content) throws SAXException, IOException { 114 if (!mHasLineNumbersSupport) { 115 return mBuilder.parse(content); 116 } else { 117 DOMParser parser = new LineNumberDOMParser(); 118 parser.parse(content); 119 return parser.getDocument(); 120 } 121 } 122 parse(File file)123 public Document parse(File file) throws SAXException, IOException { 124 return parse(new FileInputStream(file)); 125 } 126 127 private static class LineNumberDOMParser extends DOMParser { 128 private static final String FEATURE_NODE_EXPANSION = 129 "http://apache.org/xml/features/dom/defer-node-expansion"; 130 private static final String CURRENT_NODE = 131 "http://apache.org/xml/properties/dom/current-element-node"; 132 133 private XMLLocator mLocator; 134 private LinkedList<Node> mStack = new LinkedList<Node>(); 135 LineNumberDOMParser()136 private LineNumberDOMParser() { 137 try { 138 setFeature(FEATURE_NODE_EXPANSION, false); 139 } catch (SAXNotRecognizedException e) { 140 e.printStackTrace(); 141 } catch (SAXNotSupportedException e) { 142 e.printStackTrace(); 143 } 144 } 145 146 @Override startDocument(XMLLocator xmlLocator, String s, NamespaceContext namespaceContext, Augmentations augmentations)147 public void startDocument(XMLLocator xmlLocator, String s, 148 NamespaceContext namespaceContext, Augmentations augmentations) 149 throws XNIException { 150 super.startDocument(xmlLocator, s, namespaceContext, augmentations); 151 152 mLocator = xmlLocator; 153 mStack.add(setNodeLineNumber(NODE_START_LINE)); 154 } 155 setNodeLineNumber(String tag)156 private Node setNodeLineNumber(String tag) { 157 Node node = null; 158 try { 159 node = (Node) getProperty(CURRENT_NODE); 160 } catch (SAXNotRecognizedException e) { 161 e.printStackTrace(); 162 } catch (SAXNotSupportedException e) { 163 e.printStackTrace(); 164 } 165 166 if (node != null) { 167 node.setUserData(tag, mLocator.getLineNumber(), null); 168 } 169 170 return node; 171 } 172 173 @Override startElement(QName qName, XMLAttributes xmlAttributes, Augmentations augmentations)174 public void startElement(QName qName, XMLAttributes xmlAttributes, 175 Augmentations augmentations) throws XNIException { 176 super.startElement(qName, xmlAttributes, augmentations); 177 mStack.add(setNodeLineNumber(NODE_START_LINE)); 178 } 179 180 @Override endElement(QName qName, Augmentations augmentations)181 public void endElement(QName qName, Augmentations augmentations) throws XNIException { 182 super.endElement(qName, augmentations); 183 Node node = mStack.removeLast(); 184 if (node != null) { 185 node.setUserData(NODE_END_LINE, mLocator.getLineNumber(), null); 186 } 187 } 188 } 189 } 190