diff options
Diffstat (limited to 'compiler/src/main/kotlin')
4 files changed, 292 insertions, 52 deletions
diff --git a/compiler/src/main/kotlin/com/android/databinding/ext/node_ext.kt b/compiler/src/main/kotlin/com/android/databinding/ext/node_ext.kt index 97f29a3e..4ddc0794 100644 --- a/compiler/src/main/kotlin/com/android/databinding/ext/node_ext.kt +++ b/compiler/src/main/kotlin/com/android/databinding/ext/node_ext.kt @@ -17,6 +17,8 @@ package com.android.databinding.ext import org.w3c.dom.Node +import org.w3c.dom.NodeList +import java.util.ArrayList public fun Node.getAndroidId() : String? = getAttributes()?.getNamedItem("android:id")?.getNodeValue() @@ -35,3 +37,20 @@ public fun Node.getAndroidIdPath(includeRoot : Boolean) : List<String> { return ids } +public fun NodeList.forEach( f : (Node) -> Unit ) { + val cnt = getLength() + if (cnt == 0) return + for (i in 0..cnt - 1) { + f(item(i)) + } +} +public fun NodeList.toArrayList() : ArrayList<Node> { + val cnt = getLength() + val arrayList = arrayListOf<Node>() + for (i in 0..cnt - 1) { + arrayList.add(item(i)) + } + return arrayList +} + + diff --git a/compiler/src/main/kotlin/com/android/databinding/main.kt b/compiler/src/main/kotlin/com/android/databinding/main.kt index 70d69d6e..686bca36 100644 --- a/compiler/src/main/kotlin/com/android/databinding/main.kt +++ b/compiler/src/main/kotlin/com/android/databinding/main.kt @@ -48,7 +48,15 @@ import com.android.databinding.vo.LayoutExprBinding import com.android.databinding.vo.VariableScope import com.android.databinding.vo.VariableDefinition import com.android.databinding.vo.BindingTarget - +import javax.xml.namespace.NamespaceContext +import com.android.databinding.ext.forEach +import javax.xml.transform.TransformerFactory +import javax.xml.transform.stream.StreamResult +import javax.xml.transform.dom.DOMSource +import java.io.FileOutputStream +import com.android.databinding.ext.toArrayList +import com.android.databinding.util.XmlEditor +import com.android.databinding.util.Log public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Iterable<File>, @@ -57,6 +65,7 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite var dbr : DataBinderRenderer by Delegates.notNull() var styler : AttrRenderer by Delegates.notNull() val viewExprBinderRenderers = arrayListOf<ViewExprBinderRenderer>() + var processed = false public var classAnalyzer : ClassAnalyzer by Delegates.notNull() val outputResDir by Delegates.lazy { File(outputResBaseDir, "values") } @@ -69,6 +78,15 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite File(outputBaseDir.getAbsolutePath() + "/" + dbr.pkg.replace('.','/')) } + class object { + val XPATH_VARIABLE_DEFINITIONS = "//variable" + val XPATH_IMPORT_DEFINITIONS = "//import" + //val XPATH_BINDING_EXPR = "//@*[starts-with(name(), 'bind')]" + val XPATH_STATIC_BINDING_EXPR = "//@*[starts-with(name(), 'bind')]" + val XPATH_BINDING_2_EXPR = "//@*[starts-with(., '{') and substring(., string-length(.)) = '}']" + val XPATH_BINDING_ELEMENTS = "$XPATH_BINDING_2_EXPR/.." + } + public fun analyzeClasses() { viewExprBinderRenderers.forEach { it.lb.analyzeVariables() @@ -77,7 +95,14 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite fun log (s : String) = System.out.println("LOG:$s"); - public fun process() { + public fun processIfNecessary() { + if (!processed) { + processed = true + process() + } + } + + fun process() { val xmlFiles = ArrayList<File>() resourceFolders.filter({it.exists()}).forEach { findLayoutFolders(it).forEach { @@ -87,7 +112,7 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite //viewBinderRenderers.clear() for (xml in xmlFiles) { log("xmlFile $xml") - val exprBinding = parseXml3(xml) + val exprBinding = parseAndStripXml(xml) if (exprBinding == null) { log("no bindings in $xml, skipping") continue @@ -103,7 +128,7 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite public fun writeAttrFile() { outputResDir.mkdirs() - writeToFile(File(outputResDir, "bindingattrs.xml"), styler.render()) +// writeToFile(File(outputResDir, "bindingattrs.xml"), styler.render()) } public fun writeDbrFile() : Unit = writeDbrFile(dbrOutputDir) @@ -149,73 +174,133 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite private fun toLayoutId(name:String) : String = name.substring(0, name.indexOf(".")) + private fun stripBindingTags(xml : File) { + val res = XmlEditor.strip(xml) + if (res != null) { + Log.d{"file ${xml.getName()} has changed, overwriting ${xml.getAbsolutePath()}"} + xml.writeText(res) + } + } + + private fun stripFileAndGetOriginal(xml : File) : File? { + System.out.println("parsing resourceY file ${xml.getAbsolutePath()}") + val factory = DocumentBuilderFactory.newInstance() + val builder = factory.newDocumentBuilder() + val doc = builder.parse(xml) + val xPathFactory = XPathFactory.newInstance() + val xPath = xPathFactory.newXPath() + val commentElementExpr = xPath.compile("//comment()[starts-with(., \" From: file:\")][last()]") + var commentElementNodes = commentElementExpr.evaluate(doc, XPathConstants.NODESET) as NodeList + System.out.println("comment element nodes count ${commentElementNodes.getLength()}") + if (commentElementNodes.getLength() == 0) { + System.out.println("cannot find comment element to find the actual file") + return null + } + val first = commentElementNodes.item(0) + val actualFilePath = first.getNodeValue().substring(" From: file:".length()).trim() + System.out.println("actual file to parse: ${actualFilePath}") + val actualFile = File(actualFilePath) + if (!actualFile.canRead()) { + System.out.println("cannot find original, skipping. ${actualFile.getAbsolutePath()}") + return null + } + + // now if file has any binding expressions, find and delete them + val variableNodes = getVariableNodes(doc, xPath) + var changed = variableNodes.getLength() > 0 //TODO do we need to check more? + if (changed) { + stripBindingTags(xml) + } + + return actualFile + } + + private fun parseAndStripXml(xml : File) : LayoutExprBinding? { + val original = stripFileAndGetOriginal(xml) + return if (original == null) { + null + } else { + parseXml3(original) + } + } + private fun parseXml3(xml: File) : LayoutExprBinding? { + System.out.println("parsing file ${xml.getAbsolutePath()}") val factory = DocumentBuilderFactory.newInstance() val builder = factory.newDocumentBuilder() val doc = builder.parse(xml) + val xPathFactory = XPathFactory.newInstance() val xPath = xPathFactory.newXPath() // val layoutBinding = LayoutExprBinding(doc) val exprParser = ExpressionParser() - val varsExprs = xPath.compile("//@*[starts-with(name(), 'bind_var')]/..") - val varNodes = varsExprs.evaluate(doc, XPathConstants.NODESET) as NodeList - System.out.println("var node count" + varNodes.getLength()) - for (i in 0..varNodes.getLength() - 1) { - val item = varNodes.item(i) - System.out.println("checking variable node $item") + val varElementNodes = getVariableNodes(doc, xPath) + + System.out.println("varX element node count" + varElementNodes.getLength()) + val variableScope = VariableScope(doc)//all variables global for now + for (i in 0..varElementNodes.getLength() - 1) { + val item = varElementNodes.item(i) + System.out.println("checking variable element node $item") val attributes = item.getAttributes() - val variableScope = VariableScope(item) - val attrCount = attributes.getLength() - for (j in 0..(attrCount - 1)) { - val attr = attributes.item(j) - val name = attr.getNodeName() - if (name.startsWith("bind_var:")) { - variableScope.variables.add(VariableDefinition(name.substring("bind_var:".length), attr.getNodeValue())) - } - } - layoutBinding.addVariableScope(variableScope) + val variableName = attributes.getNamedItem("name").getNodeValue() + val variableType = attributes.getNamedItem("type").getNodeValue() + System.out.println("name:$variableName type:$variableType") + variableScope.variables.add(VariableDefinition(variableName, variableType)) } + layoutBinding.addVariableScope(variableScope) - val expr = xPath.compile("//@*[starts-with(name(), 'bind')]/..") - val nodes = expr.evaluate(doc, XPathConstants.NODESET) as NodeList - System.out.println("binding node count " + nodes.getLength()) - for (i in 0..nodes.getLength() - 1) { - val item = nodes.item(i) - System.out.println("checking node $item") - val attributes = item.getAttributes() + val bindingNodes = getBindingNodes(doc, xPath) + System.out.println("binding node count " + bindingNodes.getLength()) + bindingNodes.forEach { parent -> + System.out.println("binding node: $parent") + val attributes = parent.getAttributes() val id = attributes.getNamedItem("android:id") - if (id == null) { - continue - } - val bindingTarget = BindingTarget(item, id.getNodeValue(), getFullViewClassName(item.getNodeName())) - val attrCount = attributes.getLength() - for (j in 0..(attrCount - 1)) { - val attr = attributes.item(j) - val name = attr.getNodeName() - if (name.startsWith("bind:")) { - bindingTarget.addBinding(name.substring("bind:".length), exprParser.parse(attr.getNodeValue())) + if (id != null) { + val bindingTarget = BindingTarget(parent, id.getNodeValue(), getFullViewClassName(parent.getNodeName())) + val attrCount = attributes.getLength() + for (j in 0..(attrCount - 1)) { + val attr = attributes.item(j) + val value = attr.getNodeValue() + if (value.first() == '{' && value.last() == '}') { + val name = attr.getNodeName().split(":").last() + val strippedValue = value.substring(1, value.length() - 1) + bindingTarget.addBinding(name, exprParser.parse(strippedValue)) + } } + layoutBinding.bindingTargets.add(bindingTarget) } - layoutBinding.bindingTargets.add(bindingTarget) } +// System.out.println("binding node count " + nodes.getLength()) +// for (i in 0..nodes.getLength() - 1) { +// val item = nodes.item(i) +// System.out.println("checking node $item") +// val attributes = item.getAttributes() +// val id = attributes.getNamedItem("android:id") +// if (id == null) { +// continue +// } +// val bindingTarget = BindingTarget(item, id.getNodeValue(), getFullViewClassName(item.getNodeName())) +// val attrCount = attributes.getLength() +// for (j in 0..(attrCount - 1)) { +// val attr = attributes.item(j) +// val name = attr.getNodeName() +// if (name.startsWith("bind:")) { +// bindingTarget.addBinding(name.substring("bind:".length), exprParser.parse(attr.getNodeValue())) +// } +// } +// layoutBinding.bindingTargets.add(bindingTarget) +// } - val convExpr = xPath.compile("//@*[starts-with(name(), 'bind_static')]/..") - val convNodes = convExpr.evaluate(doc, XPathConstants.NODESET) as NodeList - System.out.println("converter node count " + nodes.getLength()) - for (i in 0..convNodes.getLength() - 1) { - val item = convNodes.item(i) - System.out.println("checking conv node $item") + val imports = getImportNodes(doc, xPath) + System.out.println("import node count " + imports.getLength()) + imports.forEach { item -> val attributes = item.getAttributes() - val attrCount = attributes.getLength() - for (j in 0..(attrCount - 1)) { - val attr = attributes.item(j) - val name = attr.getNodeName() - if (name.startsWith("bind_static:")) { - layoutBinding.addStaticClass(name.substring("bind_static:".length), attr.getNodeValue()) - } - } + val type = attributes.getNamedItem("type").getNodeValue() + System.out.println("type ${type}") + val alias = attributes.getNamedItem("alias")?.getNodeValue() ?: type.split("\\.").last(); + layoutBinding.addStaticClass(alias, type) } if (exprParser.model.variables.size == 0) { return null @@ -225,6 +310,21 @@ public class KLayoutParser(val appPkg : String, val resourceFolders : kotlin.Ite return layoutBinding } + private fun getBindingNodes(doc: Document, xPath: XPath): NodeList { + val expr = xPath.compile(XPATH_BINDING_ELEMENTS) + return expr.evaluate(doc, XPathConstants.NODESET) as NodeList + } + + private fun getVariableNodes(doc: Document?, xPath: XPath): NodeList { + val expr = xPath.compile(XPATH_VARIABLE_DEFINITIONS) + return expr.evaluate(doc, XPathConstants.NODESET) as NodeList + } + + private fun getImportNodes(doc: Document?, xPath: XPath): NodeList { + val expr = xPath.compile(XPATH_IMPORT_DEFINITIONS) + return expr.evaluate(doc, XPathConstants.NODESET) as NodeList + } + private fun findLayoutFolders(resources: File): Array<File> { val filenameFilter = object : FilenameFilter { override fun accept(dir: File, name: String): Boolean { diff --git a/compiler/src/main/kotlin/com/android/databinding/util/Log.kt b/compiler/src/main/kotlin/com/android/databinding/util/Log.kt index e8fe446d..28c1807a 100644 --- a/compiler/src/main/kotlin/com/android/databinding/util/Log.kt +++ b/compiler/src/main/kotlin/com/android/databinding/util/Log.kt @@ -20,4 +20,8 @@ object Log { fun d(s : String) { System.out.println("[debug] $s") } + + fun d(f : () -> String) { + System.out.println("[debug] ${f()}") + } }
\ No newline at end of file diff --git a/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt b/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt new file mode 100644 index 00000000..815108d6 --- /dev/null +++ b/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.databinding.util + +import java.io.File +import org.antlr.v4.runtime.ANTLRInputStream +import java.io.FileReader +import com.android.databinding.XMLLexer +import org.antlr.v4.runtime.CommonTokenStream +import com.android.databinding.XMLParser +import com.android.databinding.log +import com.android.databinding.XMLParserBaseVisitor +import com.android.databinding.Position +import com.android.databinding.toPosition +import com.android.databinding.toEndPosition +import java.util.Comparator + +/** + * Ugly inefficient class to strip unwanted tags from XML. + * Band-aid solution to unblock development + */ +object XmlEditor { + val reservedElementNames = arrayListOf("variable", "import") + val visitor = object : XMLParserBaseVisitor<MutableList<Pair<Position, Position>>>() { + override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList<Pair<Position, Position>>? { + log{"attr:${ctx.attrName.getText()} ${ctx.attrValue.getText()}"} + if (ctx.attrName.getText().startsWith("bind:")) { + + return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition())) + } else if (ctx.attrValue.getText().startsWith("\"{") && ctx.attrValue.getText().endsWith("}\"")) { + return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition())) + } + + //log{"visiting attr: ${ctx.getText()} at location ${ctx.getStart().toS()} ${ctx.getStop().toS()}"} + return super<XMLParserBaseVisitor>.visitAttribute(ctx) + } + + override fun visitElement(ctx: XMLParser.ElementContext): MutableList<Pair<Position, Position>>? { + log{"elm ${ctx.elmName.getText()} || ${ctx.Name()}"} + if (reservedElementNames.contains(ctx.elmName?.getText()) || ctx.elmName.getText().startsWith("bind:")) { + return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition())) + } + return super< XMLParserBaseVisitor>.visitElement(ctx) + } + + override fun defaultResult(): MutableList<Pair<Position, Position>>? = arrayListOf() + + override fun aggregateResult(aggregate: MutableList<Pair<Position, Position>>?, nextResult: MutableList<Pair<Position, Position>>?): MutableList<Pair<Position, Position>>? = + if (aggregate == null) { + nextResult + } else if (nextResult == null) { + aggregate + } else { + aggregate.addAll(nextResult) + aggregate + } + } + + fun strip(f : File) : String? { + val inputStream = ANTLRInputStream(FileReader(f)) + val lexer = XMLLexer(inputStream) + val tokenStream = CommonTokenStream(lexer) + val parser = XMLParser(tokenStream) + val expr = parser.document() + val parsedExpr = expr.accept(visitor) + if (parsedExpr.size() == 0) { + return null//nothing to strip + } + val out = StringBuilder() + val lines = f.readLines("utf-8") + lines.forEach { out.appendln(it) } + + // TODO we probably don't need to sort + val sorted = parsedExpr.sortBy(object : Comparator<Pair<Position, Position>> { + override fun compare(o1: Pair<Position, Position>, o2: Pair<Position, Position>): Int { + val lineCmp = o1.first.line.compareTo(o2.first.charIndex) + if (lineCmp != 0) { + return lineCmp + } + return o1.first.line.compareTo(o2.first.charIndex) + } + }) + var lineStarts = arrayListOf(0) + lines.withIndices().forEach { + if (it.first > 0) { + lineStarts.add(lineStarts[it.first - 1] + lines[it.first - 1].length() + 1) + } + } + + val seperator = System.lineSeparator().charAt(0) + sorted.forEach { + val posStart = lineStarts[it.first.line] + it.first.charIndex + val posEnd = lineStarts[it.second.line] + it.second.charIndex + for( i in posStart..(posEnd - 1)) { + if (out.charAt(i) != seperator) { + out.setCharAt(i, ' ') + } + } + } + + return out.toString() + } +}
\ No newline at end of file |