13bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar/* 23bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Copyright (C) 2015 The Android Open Source Project 33bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 43bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * you may not use this file except in compliance with the License. 53bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * You may obtain a copy of the License at 63bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 73bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Unless required by applicable law or agreed to in writing, software 83bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 93bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 103bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * See the License for the specific language governing permissions and 113bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * limitations under the License. 123bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar */ 133bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 14fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountpackage android.databinding.tool.store; 153bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 164ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.parser.XMLLexer; 174ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.parser.XMLParser; 184ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.parser.XMLParserBaseVisitor; 194ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.LayoutXmlProcessor; 204ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.processing.ErrorMessages; 214ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.processing.Scope; 224ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.processing.scopes.FileScopeProvider; 234ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.util.L; 244ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.util.ParserHelper; 254ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.util.Preconditions; 264ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.util.StringUtils; 274ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport android.databinding.tool.util.XmlEditor; 284ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta 29d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport com.google.common.base.Strings; 30d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount 31d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.antlr.v4.runtime.ANTLRInputStream; 32d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.antlr.v4.runtime.CommonTokenStream; 33d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.antlr.v4.runtime.ParserRuleContext; 34d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.antlr.v4.runtime.misc.NotNull; 35d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.apache.commons.io.FileUtils; 36d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.mozilla.universalchardet.UniversalDetector; 37d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.w3c.dom.Document; 38d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.w3c.dom.Node; 39d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.w3c.dom.NodeList; 40d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport org.xml.sax.SAXException; 41d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount 423bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.File; 43f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mountimport java.io.FileInputStream; 443bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.IOException; 45f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mountimport java.io.InputStreamReader; 463bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.ArrayList; 4796e1c821dd446d1ed78f8ae61131550588f60a24George Mountimport java.util.HashMap; 483bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.List; 49c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport java.util.Map; 503bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 513bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilder; 523bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilderFactory; 533bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.ParserConfigurationException; 543bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPath; 553bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathConstants; 563bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpression; 573bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpressionException; 583bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathFactory; 593bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 603bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar/** 613bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Gets the list of XML files and creates a list of 62fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mount * {@link android.databinding.tool.store.ResourceBundle} that can be persistent or converted to 633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * LayoutBinder. 643bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar */ 653bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarpublic class LayoutFileParser { 66c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 673b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount private static final String XPATH_BINDING_LAYOUT = "/layout"; 68c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 69c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private static final String LAYOUT_PREFIX = "@layout/"; 703bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 719784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar public ResourceBundle.LayoutFileBundle parseXml(final File inputFile, final File outputFile, 729784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar String pkg, final LayoutXmlProcessor.OriginalFileLookup originalFileLookup) 733bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar throws ParserConfigurationException, IOException, SAXException, 743bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar XPathExpressionException { 759784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar File originalFileFor = originalFileLookup.getOriginalFileFor(inputFile); 769784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar final String originalFilePath = originalFileFor.getAbsolutePath(); 77731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar try { 78731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Scope.enter(new FileScopeProvider() { 79731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar @Override 80731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public String provideScopeFilePath() { 819784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar return originalFilePath; 82731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 83731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar }); 849784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar final String encoding = findEncoding(inputFile); 859784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar stripFile(inputFile, outputFile, encoding, originalFileLookup); 869784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar return parseOriginalXml(originalFileFor, pkg, encoding); 87731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } finally { 88731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Scope.exit(); 893bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 90c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 913bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 92f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount private ResourceBundle.LayoutFileBundle parseOriginalXml(final File original, String pkg, 93f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount String encoding) throws IOException { 94731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar try { 95731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Scope.enter(new FileScopeProvider() { 96731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar @Override 97731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public String provideScopeFilePath() { 98731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return original.getAbsolutePath(); 99731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 100731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar }); 101731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar final String xmlNoExtension = ParserHelper.stripExtension(original.getName()); 102f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount FileInputStream fin = new FileInputStream(original); 103f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount InputStreamReader reader = new InputStreamReader(fin, encoding); 104f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount ANTLRInputStream inputStream = new ANTLRInputStream(reader); 105731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLLexer lexer = new XMLLexer(inputStream); 106731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar CommonTokenStream tokenStream = new CommonTokenStream(lexer); 107731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLParser parser = new XMLParser(tokenStream); 108731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLParser.DocumentContext expr = parser.document(); 109731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLParser.ElementContext root = expr.element(); 110731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (!"layout".equals(root.elmName.getText())) { 111731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return null; 112731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 113731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLParser.ElementContext data = getDataNode(root); 114731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar XMLParser.ElementContext rootView = getViewNode(original, root); 115731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 116731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (hasMergeInclude(rootView)) { 117731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar L.e(ErrorMessages.INCLUDE_INSIDE_MERGE); 118731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return null; 119731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 120731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar boolean isMerge = "merge".equals(rootView.elmName.getText()); 1213b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount 122731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(original, 123731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar xmlNoExtension, original.getParentFile().getName(), pkg, isMerge); 124731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension; 125731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar parseData(original, data, bundle); 126731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar parseExpressions(newTag, rootView, isMerge, bundle); 127731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return bundle; 128731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } finally { 129731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Scope.exit(); 1303e3bf43a2e11fb433b43558e2e05255edfa5b6a8George Mount } 131c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 1323bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 1339eb684f8375efd59c4fd880bd578b470eb273d41George Mount private static boolean isProcessedElement(String name) { 1344ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta if (Strings.isNullOrEmpty(name)) { 1359eb684f8375efd59c4fd880bd578b470eb273d41George Mount return false; 1369eb684f8375efd59c4fd880bd578b470eb273d41George Mount } 1379eb684f8375efd59c4fd880bd578b470eb273d41George Mount if ("view".equals(name) || "include".equals(name) || name.indexOf('.') >= 0) { 1389eb684f8375efd59c4fd880bd578b470eb273d41George Mount return true; 1399eb684f8375efd59c4fd880bd578b470eb273d41George Mount } 1409eb684f8375efd59c4fd880bd578b470eb273d41George Mount return !name.toLowerCase().equals(name); 1419eb684f8375efd59c4fd880bd578b470eb273d41George Mount } 1429eb684f8375efd59c4fd880bd578b470eb273d41George Mount 143c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private void parseExpressions(String newTag, final XMLParser.ElementContext rootView, 144c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final boolean isMerge, ResourceBundle.LayoutFileBundle bundle) { 1459784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar final List<XMLParser.ElementContext> bindingElements 1469784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar = new ArrayList<XMLParser.ElementContext>(); 1479784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar final List<XMLParser.ElementContext> otherElementsWithIds 1489784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar = new ArrayList<XMLParser.ElementContext>(); 149c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar rootView.accept(new XMLParserBaseVisitor<Void>() { 150c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar @Override 151c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar public Void visitElement(@NotNull XMLParser.ElementContext ctx) { 152c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (filter(ctx)) { 153c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar bindingElements.add(ctx); 154c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } else { 155c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String name = ctx.elmName.getText(); 1569eb684f8375efd59c4fd880bd578b470eb273d41George Mount if (isProcessedElement(name) && 157c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar attributeMap(ctx).containsKey("android:id")) { 158c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar otherElementsWithIds.add(ctx); 159c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 160c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 161c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar visitChildren(ctx); 162c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return null; 1633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 1643bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 165c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private boolean filter(XMLParser.ElementContext ctx) { 166c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (isMerge) { 167c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar // account for XMLParser.ContentContext 168c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (ctx.getParent().getParent() == rootView) { 169c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return true; 170c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 171c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } else if (ctx == rootView) { 172c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return true; 173c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 1748510dab6a17ccd59a2a9cbc07f900dfc2f11ff2bGeorge Mount return hasIncludeChild(ctx) || XmlEditor.hasExpressionAttributes(ctx) || 1758510dab6a17ccd59a2a9cbc07f900dfc2f11ff2bGeorge Mount "include".equals(ctx.elmName.getText()); 176c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 177c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 178c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private boolean hasIncludeChild(XMLParser.ElementContext ctx) { 179c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext child : XmlEditor.elements(ctx)) { 180c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if ("include".equals(child.elmName.getText())) { 181c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return true; 182c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 183c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 184c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return false; 185c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 186c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar }); 187c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 188c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final HashMap<XMLParser.ElementContext, String> nodeTagMap = 189c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar new HashMap<XMLParser.ElementContext, String>(); 190c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar L.d("number of binding nodes %d", bindingElements.size()); 1913b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount int tagNumber = 0; 192c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext parent : bindingElements) { 193c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final Map<String, String> attributes = attributeMap(parent); 194c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String nodeName = parent.elmName.getText(); 1957ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount String viewName = null; 1968e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount String includedLayoutName = null; 197c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final String id = attributes.get("android:id"); 19896e1c821dd446d1ed78f8ae61131550588f60a24George Mount final String tag; 199c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final String originalTag = attributes.get("android:tag"); 2008e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount if ("include".equals(nodeName)) { 2018e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount // get the layout attribute 202c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final String includeValue = attributes.get("layout"); 2034ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta if (Strings.isNullOrEmpty(includeValue)) { 204c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar L.e("%s must include a layout", parent); 2050c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar } 2060c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar if (!includeValue.startsWith(LAYOUT_PREFIX)) { 207c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar L.e("included value (%s) must start with %s.", 208c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar includeValue, LAYOUT_PREFIX); 2090c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar } 2108e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount // if user is binding something there, there MUST be a layout file to be 2118e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount // generated. 2129784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar includedLayoutName = includeValue.substring(LAYOUT_PREFIX.length()); 213c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final ParserRuleContext myParentContent = parent.getParent(); 214c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.check(myParentContent instanceof XMLParser.ContentContext, 215c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar "parent of an include tag must be a content context but it is %s", 216c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar myParentContent.getClass().getCanonicalName()); 217c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final ParserRuleContext grandParent = myParentContent.getParent(); 218c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.check(grandParent instanceof XMLParser.ElementContext, 219c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar "grandparent of an include tag must be an element context but it is %s", 220c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar grandParent.getClass().getCanonicalName()); 221c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar //noinspection SuspiciousMethodCalls 222c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar tag = nodeTagMap.get(grandParent); 2234d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount } else if ("fragment".equals(nodeName)) { 2246a5a45a07b074230c07f78f271e417c7f844c1ecGeorge Mount if (XmlEditor.hasExpressionAttributes(parent)) { 2256a5a45a07b074230c07f78f271e417c7f844c1ecGeorge Mount L.e("fragments do not support data binding expressions."); 2266a5a45a07b074230c07f78f271e417c7f844c1ecGeorge Mount } 227d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount continue; 2283bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } else { 2297ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount viewName = getViewName(parent); 230c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar // account for XMLParser.ContentContext 231c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (rootView == parent || (isMerge && parent.getParent().getParent() == rootView)) { 2323b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount tag = newTag + "_" + tagNumber; 23396e1c821dd446d1ed78f8ae61131550588f60a24George Mount } else { 2347ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount tag = "binding_" + tagNumber; 23596e1c821dd446d1ed78f8ae61131550588f60a24George Mount } 2363b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount tagNumber++; 2378e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount } 2388e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount final ResourceBundle.BindingTargetBundle bindingTargetBundle = 239c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar bundle.createBindingTarget(id, viewName, true, tag, originalTag, 240c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar new Location(parent)); 24196e1c821dd446d1ed78f8ae61131550588f60a24George Mount nodeTagMap.put(parent, tag); 2428e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount bindingTargetBundle.setIncludedLayout(includedLayoutName); 2438e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount 244c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.AttributeContext attr : XmlEditor.expressionAttributes(parent)) { 245c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String value = escapeQuotes(attr.attrValue.getText(), true); 246d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount final boolean isOneWay = value.startsWith("@{"); 247d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount final boolean isTwoWay = value.startsWith("@={"); 248d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount if (isOneWay || isTwoWay) { 249d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount if (value.charAt(value.length() - 1) != '}') { 250d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount L.e("Expecting '}' in expression '%s'", attr.attrValue.getText()); 251d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount } 252d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount final int startIndex = isTwoWay ? 3 : 2; 253d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount final int endIndex = value.length() - 1; 254d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount final String strippedValue = value.substring(startIndex, endIndex); 255731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Location attrLocation = new Location(attr); 256731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Location valueLocation = new Location(); 257731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar // offset to 0 based 258731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar valueLocation.startLine = attr.attrValue.getLine() - 1; 259731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar valueLocation.startOffset = attr.attrValue.getCharPositionInLine() + 260731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar attr.attrValue.getText().indexOf(strippedValue); 261731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar valueLocation.endLine = attrLocation.endLine; 262731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar valueLocation.endOffset = attrLocation.endOffset - 2; // account for: "} 263d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount bindingTargetBundle.addBinding(escapeQuotes(attr.attrName.getText(), false), 264d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount strippedValue, isTwoWay, attrLocation, valueLocation); 2658e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount } 2663bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 2673bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 2683bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 269c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext elm : otherElementsWithIds) { 270c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final String id = attributeMap(elm).get("android:id"); 271c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final String className = getViewName(elm); 272c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar bundle.createBindingTarget(id, className, true, null, null, new Location(elm)); 27395d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount } 2743bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 2753bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 276c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private String getViewName(XMLParser.ElementContext elm) { 277c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String viewName = elm.elmName.getText(); 278c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if ("view".equals(viewName)) { 279c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String classNode = attributeMap(elm).get("class"); 2804ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta if (Strings.isNullOrEmpty(classNode)) { 281c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar L.e("No class attribute for 'view' node"); 282c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 283c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar viewName = classNode; 2845d8732072543d6f6fc1aa8f8a64654bcd1784347George Mount } else if ("include".equals(viewName) && !XmlEditor.hasExpressionAttributes(elm)) { 2855d8732072543d6f6fc1aa8f8a64654bcd1784347George Mount viewName = "android.view.View"; 2863b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount } 287c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return viewName; 2883b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount } 2893b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount 290c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private void parseData(File xml, XMLParser.ElementContext data, 291c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar ResourceBundle.LayoutFileBundle bundle) { 292c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (data == null) { 293c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return; 2943b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount } 295c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext imp : filter(data, "import")) { 296c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final Map<String, String> attrMap = attributeMap(imp); 297c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String type = attrMap.get("type"); 298c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String alias = attrMap.get("alias"); 299c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.check(StringUtils.isNotBlank(type), "Type of an import cannot be empty." 300c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar + " %s in %s", imp.toStringTree(), xml); 3014ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta if (Strings.isNullOrEmpty(alias)) { 3024ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta alias = type.substring(type.lastIndexOf('.') + 1); 303c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 304c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar bundle.addImport(alias, type, new Location(imp)); 3053b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount } 3063bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 307c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext variable : filter(data, "variable")) { 308c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final Map<String, String> attrMap = attributeMap(variable); 309c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String type = attrMap.get("type"); 310c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String name = attrMap.get("name"); 311c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.checkNotNull(type, "variable must have a type definition %s in %s", 312c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar variable.toStringTree(), xml); 313c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.checkNotNull(name, "variable must have a name %s in %s", 314c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar variable.toStringTree(), xml); 31523910cf498c35704a03ba4f3889de2ab97ccbe21George Mount bundle.addVariable(name, type, new Location(variable), true); 316c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 317aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar final XMLParser.AttributeContext className = findAttribute(data, "class"); 318aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar if (className != null) { 319aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar final String name = escapeQuotes(className.attrValue.getText(), true); 320aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar if (StringUtils.isNotBlank(name)) { 321aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar Location location = new Location( 322aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar className.attrValue.getLine() - 1, 323aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar className.attrValue.getCharPositionInLine() + 1, 324aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar className.attrValue.getLine() - 1, 325aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar className.attrValue.getCharPositionInLine() + name.length() 326aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar ); 327aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar bundle.setBindingClass(name, location); 328aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 329c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 3303bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 3313bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 332c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private XMLParser.ElementContext getDataNode(XMLParser.ElementContext root) { 333c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final List<XMLParser.ElementContext> data = filter(root, "data"); 334c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (data.size() == 0) { 335c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return null; 336c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 337c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.check(data.size() == 1, "XML layout can have only 1 data tag"); 338c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return data.get(0); 33995d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount } 34095d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount 341c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private XMLParser.ElementContext getViewNode(File xml, XMLParser.ElementContext root) { 342c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final List<XMLParser.ElementContext> view = filterNot(root, "data"); 343c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar Preconditions.check(view.size() == 1, "XML layout %s must have 1 view but has %s. root" 344c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar + " children count %s", xml, view.size(), root.getChildCount()); 345c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return view.get(0); 34696e1c821dd446d1ed78f8ae61131550588f60a24George Mount } 34796e1c821dd446d1ed78f8ae61131550588f60a24George Mount 348c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private List<XMLParser.ElementContext> filter(XMLParser.ElementContext root, 349c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String name) { 3509784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar List<XMLParser.ElementContext> result = new ArrayList<XMLParser.ElementContext>(); 351c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (root == null) { 352c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 353c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 354c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final XMLParser.ContentContext content = root.content(); 355c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (content == null) { 356c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 357c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 358c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext child : XmlEditor.elements(root)) { 359c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (name.equals(child.elmName.getText())) { 360c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar result.add(child); 361c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 3623bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 3633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar return result; 3643bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 3653bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 366c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private List<XMLParser.ElementContext> filterNot(XMLParser.ElementContext root, 367c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String name) { 3689784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar List<XMLParser.ElementContext> result = new ArrayList<XMLParser.ElementContext>(); 369c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (root == null) { 370c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 371c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 372c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final XMLParser.ContentContext content = root.content(); 373c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (content == null) { 374c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 375c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 376c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.ElementContext child : XmlEditor.elements(root)) { 377c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (!name.equals(child.elmName.getText())) { 378c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar result.add(child); 37995d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount } 38095d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount } 381c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 3823bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 3833bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 384c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private boolean hasMergeInclude(XMLParser.ElementContext rootView) { 385c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return "merge".equals(rootView.elmName.getText()) && filter(rootView, "include").size() > 0; 3863bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 3873bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar 3889784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar private void stripFile(File xml, File out, String encoding, 3899784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar LayoutXmlProcessor.OriginalFileLookup originalFileLookup) 3903bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar throws ParserConfigurationException, IOException, SAXException, 3913bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar XPathExpressionException { 3923bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 3933bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar DocumentBuilder builder = factory.newDocumentBuilder(); 3943bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar Document doc = builder.parse(xml); 3953bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar XPathFactory xPathFactory = XPathFactory.newInstance(); 3963bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar XPath xPath = xPathFactory.newXPath(); 397890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar File actualFile = originalFileLookup == null ? null 398890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar : originalFileLookup.getOriginalFileFor(xml); 3999784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar // TODO get rid of original file lookup 400890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar if (actualFile == null) { 4019784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar actualFile = xml; 402890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar } 403ddeffcc2d89275528b2001836da2795b14ea7909Yigit Boyar // always create id from actual file when available. Gradle may duplicate files. 404ddeffcc2d89275528b2001836da2795b14ea7909Yigit Boyar String noExt = ParserHelper.stripExtension(actualFile.getName()); 405ddeffcc2d89275528b2001836da2795b14ea7909Yigit Boyar String binderId = actualFile.getParentFile().getName() + '/' + noExt; 406890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar // now if file has any binding expressions, find and delete them 407890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar boolean changed = isBindingLayout(doc, xPath); 408890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar if (changed) { 4099784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar stripBindingTags(xml, out, binderId, encoding); 4109784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar } else if (!xml.equals(out)){ 4119784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar FileUtils.copyFile(xml, out); 412890b4850c628f04eb75397e427ba7074e4f9c386Yigit Boyar } 4133bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar } 41475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar 415c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private boolean isBindingLayout(Document doc, XPath xPath) throws XPathExpressionException { 416c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return !get(doc, xPath, XPATH_BINDING_LAYOUT).isEmpty(); 417c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 418c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 419c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private List<Node> get(Document doc, XPath xPath, String pattern) 420c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar throws XPathExpressionException { 421c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar final XPathExpression expr = xPath.compile(pattern); 422c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET)); 423c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 424c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 425c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private List<Node> toList(NodeList nodeList) { 426c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar List<Node> result = new ArrayList<Node>(); 427c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (int i = 0; i < nodeList.getLength(); i++) { 428c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar result.add(nodeList.item(i)); 429c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 430c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 431c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 432c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 4339784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar private void stripBindingTags(File xml, File output, String newTag, String encoding) throws IOException { 434f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount String res = XmlEditor.strip(xml, newTag, encoding); 4359784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar Preconditions.checkNotNull(res, "layout file should've changed %s", xml.getAbsolutePath()); 436c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (res != null) { 437c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar L.d("file %s has changed, overwriting %s", xml.getName(), xml.getAbsolutePath()); 4389784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar FileUtils.writeStringToFile(output, res, encoding); 439f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount } 440f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount } 441f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount 442f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount private static String findEncoding(File f) throws IOException { 443f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount FileInputStream fin = new FileInputStream(f); 444f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount try { 445f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount UniversalDetector universalDetector = new UniversalDetector(null); 446f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount 447f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount byte[] buf = new byte[4096]; 448f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount int nread; 449f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount while ((nread = fin.read(buf)) > 0 && !universalDetector.isDone()) { 450f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount universalDetector.handleData(buf, 0, nread); 451f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount } 452f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount 453f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount universalDetector.dataEnd(); 454f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount 455f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount String encoding = universalDetector.getDetectedCharset(); 456f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount if (encoding == null) { 457f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount encoding = "utf-8"; 458f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount } 459f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount return encoding; 460f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount } finally { 461f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount fin.close(); 462c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 46375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar } 46475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar 465c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private static Map<String, String> attributeMap(XMLParser.ElementContext root) { 4669784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar final Map<String, String> result = new HashMap<String, String>(); 467c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar for (XMLParser.AttributeContext attr : XmlEditor.attributes(root)) { 468c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar result.put(escapeQuotes(attr.attrName.getText(), false), 469c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar escapeQuotes(attr.attrValue.getText(), true)); 470c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 471c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return result; 472c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 473c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar 474aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar private static XMLParser.AttributeContext findAttribute(XMLParser.ElementContext element, 475aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar String name) { 476aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar for (XMLParser.AttributeContext attr : element.attribute()) { 477aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar if (escapeQuotes(attr.attrName.getText(), false).equals(name)) { 478aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar return attr; 479aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 480aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 481aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar return null; 482aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 483aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar 484c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar private static String escapeQuotes(String textWithQuotes, boolean unescapeValue) { 485c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar char first = textWithQuotes.charAt(0); 486c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar int start = 0, end = textWithQuotes.length(); 487c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (first == '"' || first == '\'') { 488c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar start = 1; 489c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 490c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar char last = textWithQuotes.charAt(textWithQuotes.length() - 1); 491c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (last == '"' || last == '\'') { 492c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar end -= 1; 493c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 494c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar String val = textWithQuotes.substring(start, end); 495c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar if (unescapeValue) { 4964ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta return StringUtils.unescapeXml(val); 497c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } else { 498c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar return val; 499c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 500c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar } 5013bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar} 502