xref: /aosp_15_r20/tools/metalava/metalava/src/main/java/com/android/tools/metalava/xml/Xml.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
1 /*
2  * Copyright (C) 2023 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.tools.metalava.xml
18 
19 import com.android.utils.XmlUtils.stripBom
20 import java.io.PrintWriter
21 import java.io.Reader
22 import java.io.StringReader
23 import javax.xml.parsers.DocumentBuilder
24 import javax.xml.parsers.DocumentBuilderFactory
25 import javax.xml.parsers.ParserConfigurationException
26 import org.w3c.dom.Document
27 import org.xml.sax.ErrorHandler
28 import org.xml.sax.InputSource
29 import org.xml.sax.SAXParseException
30 
31 // XML parser features
32 private const val LOAD_EXTERNAL_DTD =
33     "http://apache.org/xml/features/nonvalidating/load-external-dtd"
34 private const val EXTERNAL_PARAMETER_ENTITIES =
35     "http://xml.org/sax/features/external-parameter-entities"
36 private const val EXTERNAL_GENERAL_ENTITIES =
37     "http://xml.org/sax/features/external-general-entities"
38 
39 /**
40  * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
41  * validate, and is optionally namespace aware.
42  *
43  * @param xml the XML content to be parsed (must be well-formed)
44  * @param namespaceAware whether the parser is namespace aware
45  * @param errorWriter the writer to write errors to
46  * @return the DOM document
47  */
parseDocumentnull48 fun parseDocument(
49     xml: String,
50     namespaceAware: Boolean,
51     errorWriter: PrintWriter = PrintWriter(System.out)
52 ): Document {
53     return parseDocument(StringReader(stripBom(xml)), namespaceAware, errorWriter)
54 }
55 
56 /**
57  * Parses the given [Reader] as a DOM document, using the JDK parser. The parser does not validate,
58  * and is optionally namespace aware.
59  *
60  * @param xml a reader for the XML content to be parsed (must be well-formed)
61  * @param namespaceAware whether the parser is namespace aware
62  * @param errorWriter the writer to write errors to
63  * @return the DOM document
64  */
parseDocumentnull65 fun parseDocument(
66     xml: Reader,
67     namespaceAware: Boolean,
68     errorWriter: PrintWriter = PrintWriter(System.out)
69 ): Document {
70     val inputStream = InputSource(xml)
71     val builder = createDocumentBuilder(namespaceAware)
72     builder.setErrorHandler(
73         object : ErrorHandler {
74             override fun warning(exception: SAXParseException) {
75                 errorWriter.println("${exception.lineNumber}: Warning: ${exception.message}")
76             }
77 
78             override fun error(exception: SAXParseException) {
79                 errorWriter.println("${exception.lineNumber}: Error: ${exception.message}")
80             }
81 
82             override fun fatalError(exception: SAXParseException) {
83                 error(exception)
84             }
85         }
86     )
87     return builder.parse(inputStream)
88 }
89 
90 /** Creates a preconfigured document builder. */
createDocumentBuildernull91 private fun createDocumentBuilder(namespaceAware: Boolean): DocumentBuilder {
92     try {
93         val factory = DocumentBuilderFactory.newInstance()
94         factory.isNamespaceAware = namespaceAware
95         factory.isValidating = false
96         factory.setFeature(EXTERNAL_GENERAL_ENTITIES, false)
97         factory.setFeature(EXTERNAL_PARAMETER_ENTITIES, false)
98         factory.setFeature(LOAD_EXTERNAL_DTD, false)
99         return factory.newDocumentBuilder()
100     } catch (e: ParserConfigurationException) {
101         throw Error(e) // Impossible in the current context.
102     }
103 }
104