173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets/*
273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * Copyright 2017 The Android Open Source Project
373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets *
473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * Licensed under the Apache License, Version 2.0 (the "License");
573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * you may not use this file except in compliance with the License.
673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * You may obtain a copy of the License at
773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets *
873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets *      http://www.apache.org/licenses/LICENSE-2.0
973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets *
1073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * Unless required by applicable law or agreed to in writing, software
1173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * distributed under the License is distributed on an "AS IS" BASIS,
1273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * See the License for the specific language governing permissions and
1473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets * limitations under the License.
1573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets */
1673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
1773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinetspackage androidx.navigation.safe.args.generator
1873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
1973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinetsimport org.xmlpull.v1.XmlPullParser
2073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinetsimport org.xmlpull.v1.XmlPullParserFactory
2173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinetsimport java.io.Reader
2273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
2328f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinetsinternal class XmlPositionParser(private val name: String, reader: Reader, val logger: NavLogger) {
2473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    private var startLine = 0
2573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    private var startColumn = 0
2673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    private val parser: XmlPullParser = XmlPullParserFactory.newInstance().newPullParser().apply {
2773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
2873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        setInput(reader)
2973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    }
3073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
3173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    fun name(): String = parser.name
3273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
3373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    fun traverseStartTags(onStartTag: () -> Boolean) {
3473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        while (parser.eventType != XmlPullParser.END_DOCUMENT) {
3573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            val processedLine = parser.lineNumber
3673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            val processedColumn = parser.columnNumber
3773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            if (parser.eventType == XmlPullParser.START_TAG) {
3873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                if (onStartTag()) {
3973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                    return
4073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                }
4173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            }
4273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
4373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            if (processedLine == parser.lineNumber && processedColumn == parser.columnNumber) {
4473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                // otherwise onStart already called next() and we need to try to process current node
4573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                nextToken()
4673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            }
4773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        }
4873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    }
4973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
5073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    private fun nextToken() {
5173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        startLine = parser.lineNumber
5273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        startColumn = parser.columnNumber
5373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        parser.nextToken()
5473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    }
5573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
5628f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets    fun xmlPosition() = XmlPosition(name, startLine, startColumn - 1)
5773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
5873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    fun traverseInnerStartTags(onStartTag: () -> Unit) {
5973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        val innerDepth = parser.depth + 1
6073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        nextToken()
6173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        traverseStartTags {
6273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            if (innerDepth == parser.depth) {
6373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets                onStartTag()
6473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            }
6573d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            parser.depth < innerDepth
6673d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        }
6773d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    }
6873d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
6973d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    fun attrValue(namespace: String, name: String): String? =
7073d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        (0 until parser.attributeCount).find {
7173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets            parser.getAttributeNamespace(it) == namespace && name == parser.getAttributeName(it)
7273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets        }?.let { parser.getAttributeValue(it) }
7373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
7428f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets    fun attrValueOrError(namespace: String, attrName: String): String? {
7528f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets        val value = attrValue(namespace, attrName)
7628f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets        if (value == null) {
7728f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets            logger.error(mandatoryAttrMissingError(name(), attrName), xmlPosition())
7828f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets        }
7928f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets        return value
8028f10aef67d29479eb0821b63967450e1bcee8e4Sergey Vasilinets    }
8173d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets}
8273d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets
8373d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinetsinternal fun mandatoryAttrMissingError(tag: String, attr: String) =
8473d7c77fb8b34198d06bf1d0f672accbdcac61a9Sergey Vasilinets    "Mandatory attribute '$attr' for tag '$tag' is missing."