summaryrefslogtreecommitdiff
path: root/compiler/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src')
-rw-r--r--compiler/src/main/kotlin/com/android/databinding/ext/node_ext.kt19
-rw-r--r--compiler/src/main/kotlin/com/android/databinding/main.kt204
-rw-r--r--compiler/src/main/kotlin/com/android/databinding/util/Log.kt4
-rw-r--r--compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt117
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