1/*
2 * Copyright 2017 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
17package androidx.navigation.safe.args.generator
18
19import org.xmlpull.v1.XmlPullParser
20import org.xmlpull.v1.XmlPullParserFactory
21import java.io.Reader
22
23internal class XmlPositionParser(private val name: String, reader: Reader, val logger: NavLogger) {
24    private var startLine = 0
25    private var startColumn = 0
26    private val parser: XmlPullParser = XmlPullParserFactory.newInstance().newPullParser().apply {
27        setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
28        setInput(reader)
29    }
30
31    fun name(): String = parser.name
32
33    fun traverseStartTags(onStartTag: () -> Boolean) {
34        while (parser.eventType != XmlPullParser.END_DOCUMENT) {
35            val processedLine = parser.lineNumber
36            val processedColumn = parser.columnNumber
37            if (parser.eventType == XmlPullParser.START_TAG) {
38                if (onStartTag()) {
39                    return
40                }
41            }
42
43            if (processedLine == parser.lineNumber && processedColumn == parser.columnNumber) {
44                // otherwise onStart already called next() and we need to try to process current node
45                nextToken()
46            }
47        }
48    }
49
50    private fun nextToken() {
51        startLine = parser.lineNumber
52        startColumn = parser.columnNumber
53        parser.nextToken()
54    }
55
56    fun xmlPosition() = XmlPosition(name, startLine, startColumn - 1)
57
58    fun traverseInnerStartTags(onStartTag: () -> Unit) {
59        val innerDepth = parser.depth + 1
60        nextToken()
61        traverseStartTags {
62            if (innerDepth == parser.depth) {
63                onStartTag()
64            }
65            parser.depth < innerDepth
66        }
67    }
68
69    fun attrValue(namespace: String, name: String): String? =
70        (0 until parser.attributeCount).find {
71            parser.getAttributeNamespace(it) == namespace && name == parser.getAttributeName(it)
72        }?.let { parser.getAttributeValue(it) }
73
74    fun attrValueOrError(namespace: String, attrName: String): String? {
75        val value = attrValue(namespace, attrName)
76        if (value == null) {
77            logger.error(mandatoryAttrMissingError(name(), attrName), xmlPosition())
78        }
79        return value
80    }
81}
82
83internal fun mandatoryAttrMissingError(tag: String, attr: String) =
84    "Mandatory attribute '$attr' for tag '$tag' is missing."