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