LayoutFileParser.java revision c1560e6b00b398867da12fbdc5a1fcd1d50b801c
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
16c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport org.antlr.v4.runtime.ANTLRInputStream;
17c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport org.antlr.v4.runtime.CommonTokenStream;
18c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport org.antlr.v4.runtime.ParserRuleContext;
19c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport org.antlr.v4.runtime.misc.NotNull;
203bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.apache.commons.io.FileUtils;
21c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport org.apache.commons.lang3.StringEscapeUtils;
223bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.apache.commons.lang3.StringUtils;
233bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.Document;
243bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.Node;
253bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.w3c.dom.NodeList;
263bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport org.xml.sax.SAXException;
273bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
28c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport android.databinding.parser.XMLLexer;
29c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport android.databinding.parser.XMLParser;
30c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport android.databinding.parser.XMLParserBaseVisitor;
31fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.L;
32fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.ParserHelper;
33c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport android.databinding.tool.util.Preconditions;
34fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.XmlEditor;
35fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mount
363bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.File;
37c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport java.io.FileReader;
383bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.io.IOException;
3975da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.MalformedURLException;
4075da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.URISyntaxException;
4175da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyarimport java.net.URL;
423bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.ArrayList;
4396e1c821dd446d1ed78f8ae61131550588f60a24George Mountimport java.util.HashMap;
443bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport java.util.List;
45c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyarimport java.util.Map;
463bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
473bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilder;
483bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.DocumentBuilderFactory;
493bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.parsers.ParserConfigurationException;
503bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPath;
513bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathConstants;
523bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpression;
533bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathExpressionException;
543bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarimport javax.xml.xpath.XPathFactory;
553bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
563bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar/**
573bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * Gets the list of XML files and creates a list of
58fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mount * {@link android.databinding.tool.store.ResourceBundle} that can be persistent or converted to
593bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar * LayoutBinder.
603bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar */
613bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyarpublic class LayoutFileParser {
62c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
633b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    private static final String XPATH_BINDING_LAYOUT = "/layout";
64c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
65c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private static final String LAYOUT_PREFIX = "@layout/";
663bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
674d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount    public ResourceBundle.LayoutFileBundle parseXml(File xml, String pkg, int minSdk)
683bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            throws ParserConfigurationException, IOException, SAXException,
693bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            XPathExpressionException {
700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        final String xmlNoExtension = ParserHelper.stripExtension(xml.getName());
710390898cf7c4fcad255e8cfd6802f722b516cb2cGeorge Mount        final String newTag = xml.getParentFile().getName() + '/' + xmlNoExtension;
720390898cf7c4fcad255e8cfd6802f722b516cb2cGeorge Mount        File original = stripFileAndGetOriginal(xml, newTag);
733bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (original == null) {
74895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar            L.d("assuming the file is the original for %s", xml.getAbsoluteFile());
75895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar            original = xml;
763bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
77c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return parseXml(original, pkg);
78c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
793bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
80c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private ResourceBundle.LayoutFileBundle parseXml(File original, String pkg)
81c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            throws IOException {
82c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final String xmlNoExtension = ParserHelper.stripExtension(original.getName());
83c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        ANTLRInputStream inputStream = new ANTLRInputStream(new FileReader(original));
84c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLLexer lexer = new XMLLexer(inputStream);
85c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
86c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLParser parser = new XMLParser(tokenStream);
87c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLParser.DocumentContext expr = parser.document();
88c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLParser.ElementContext root = expr.element();
89c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (!"layout".equals(root.elmName.getText())) {
903b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount            return null;
913b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
92c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLParser.ElementContext data = getDataNode(root);
93c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        XMLParser.ElementContext rootView = getViewNode(original, root);
943b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
95c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (hasMergeInclude(rootView)) {
963e3bf43a2e11fb433b43558e2e05255edfa5b6a8George Mount            L.e("Data binding does not support include elements as direct children of a " +
97c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    "merge element: %s", original.getPath());
983e3bf43a2e11fb433b43558e2e05255edfa5b6a8George Mount            return null;
993e3bf43a2e11fb433b43558e2e05255edfa5b6a8George Mount        }
100c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        boolean isMerge = "merge".equals(rootView.elmName.getText());
10196e1c821dd446d1ed78f8ae61131550588f60a24George Mount
10296e1c821dd446d1ed78f8ae61131550588f60a24George Mount        ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(
103c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                xmlNoExtension, original.getParentFile().getName(), pkg, isMerge);
104c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension;
105c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        parseData(original, data, bundle);
106c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        parseExpressions(newTag, rootView, isMerge, bundle);
107c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return bundle;
108c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
1093bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
110c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private void parseExpressions(String newTag, final XMLParser.ElementContext rootView,
111c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final boolean isMerge, ResourceBundle.LayoutFileBundle bundle) {
112c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final List<XMLParser.ElementContext> bindingElements = new ArrayList<>();
113c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final List<XMLParser.ElementContext> otherElementsWithIds = new ArrayList<>();
114c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        rootView.accept(new XMLParserBaseVisitor<Void>() {
115c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            @Override
116c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            public Void visitElement(@NotNull XMLParser.ElementContext ctx) {
117c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (filter(ctx)) {
118c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    bindingElements.add(ctx);
119c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                } else {
120c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    String name = ctx.elmName.getText();
121c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    if (!"include".equals(name) && !"fragment".equals(name) &&
122c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                            attributeMap(ctx).containsKey("android:id")) {
123c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        otherElementsWithIds.add(ctx);
124c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    }
125c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                }
126c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                visitChildren(ctx);
127c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                return null;
1283bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            }
1293bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
130c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            private boolean filter(XMLParser.ElementContext ctx) {
131c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (isMerge) {
132c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    // account for XMLParser.ContentContext
133c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    if (ctx.getParent().getParent() == rootView) {
134c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        return true;
135c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    }
136c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                } else if (ctx == rootView) {
137c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    return true;
138c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                }
139c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (hasIncludeChild(ctx)) {
140c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    return true;
141c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                }
142c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (XmlEditor.hasExpressionAttributes(ctx)) {
143c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    return true;
144c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                }
145c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                return false;
146c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            }
147c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
148c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            private boolean hasIncludeChild(XMLParser.ElementContext ctx) {
149c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                for (XMLParser.ElementContext child : XmlEditor.elements(ctx)) {
150c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    if ("include".equals(child.elmName.getText())) {
151c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        return true;
152c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    }
153c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                }
154c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                return false;
155c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            }
156c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        });
157c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
158c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final HashMap<XMLParser.ElementContext, String> nodeTagMap =
159c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                new HashMap<XMLParser.ElementContext, String>();
160c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        L.d("number of binding nodes %d", bindingElements.size());
1613b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        int tagNumber = 0;
162c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext parent : bindingElements) {
163c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final Map<String, String> attributes = attributeMap(parent);
164c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String nodeName = parent.elmName.getText();
1657ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount            String viewName = null;
1668e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            String includedLayoutName = null;
167c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final String id = attributes.get("android:id");
16896e1c821dd446d1ed78f8ae61131550588f60a24George Mount            final String tag;
169c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final String originalTag = attributes.get("android:tag");
1708e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            if ("include".equals(nodeName)) {
1718e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // get the layout attribute
172c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                final String includeValue = attributes.get("layout");
173c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (StringUtils.isEmpty(includeValue)) {
174c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    L.e("%s must include a layout", parent);
1750c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                }
1760c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                if (!includeValue.startsWith(LAYOUT_PREFIX)) {
177c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    L.e("included value (%s) must start with %s.",
178c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                            includeValue, LAYOUT_PREFIX);
1790c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                }
1808e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // if user is binding something there, there MUST be a layout file to be
1818e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                // generated.
1828e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                String layoutName = includeValue.substring(LAYOUT_PREFIX.length());
1838e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                includedLayoutName = layoutName;
184c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                final ParserRuleContext myParentContent = parent.getParent();
185c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                Preconditions.check(myParentContent instanceof XMLParser.ContentContext,
186c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        "parent of an include tag must be a content context but it is %s",
187c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        myParentContent.getClass().getCanonicalName());
188c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                final ParserRuleContext grandParent = myParentContent.getParent();
189c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                Preconditions.check(grandParent instanceof XMLParser.ElementContext,
190c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        "grandparent of an include tag must be an element context but it is %s",
191c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        grandParent.getClass().getCanonicalName());
192c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                //noinspection SuspiciousMethodCalls
193c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                tag = nodeTagMap.get(grandParent);
1944d4979490e1fa374c0d7f3599fed0a9e83a579d0George Mount            } else if ("fragment".equals(nodeName)) {
195c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                L.e("fragments do not support data binding expressions.");
196d6527ee28cc3aa05818799af8def9593346f91bcGeorge Mount                continue;
1973bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            } else {
1987ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                viewName = getViewName(parent);
199c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                // account for XMLParser.ContentContext
200c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                if (rootView == parent || (isMerge && parent.getParent().getParent() == rootView)) {
2013b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount                    tag = newTag + "_" + tagNumber;
20296e1c821dd446d1ed78f8ae61131550588f60a24George Mount                } else {
2037ff60c24c6de7ba0c674fe65a82ad4a88dab2e5dGeorge Mount                    tag = "binding_" + tagNumber;
20496e1c821dd446d1ed78f8ae61131550588f60a24George Mount                }
2053b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount                tagNumber++;
2068e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            }
2078e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            final ResourceBundle.BindingTargetBundle bindingTargetBundle =
208c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    bundle.createBindingTarget(id, viewName, true, tag, originalTag,
209c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                            new Location(parent));
21096e1c821dd446d1ed78f8ae61131550588f60a24George Mount            nodeTagMap.put(parent, tag);
2118e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount            bindingTargetBundle.setIncludedLayout(includedLayoutName);
2128e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount
213c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            for (XMLParser.AttributeContext attr : XmlEditor.expressionAttributes(parent)) {
214c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                String value = escapeQuotes(attr.attrValue.getText(), true);
2158e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
2168e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                        value.charAt(value.length() - 1) == '}') {
2178e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                    final String strippedValue = value.substring(2, value.length() - 1);
218c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    bindingTargetBundle.addBinding(escapeQuotes(attr.attrName.getText(), false)
219c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                            , strippedValue);
2208e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount                }
2213bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            }
2223bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
2233bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
224c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext elm : otherElementsWithIds) {
225c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final String id = attributeMap(elm).get("android:id");
226c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final String className = getViewName(elm);
227c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            bundle.createBindingTarget(id, className, true, null, null, new Location(elm));
22895d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        }
2293bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2303bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
231c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private String getViewName(XMLParser.ElementContext elm) {
232c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        String viewName = elm.elmName.getText();
233c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if ("view".equals(viewName)) {
234c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String classNode = attributeMap(elm).get("class");
235c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            if (StringUtils.isEmpty(classNode)) {
236c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                L.e("No class attribute for 'view' node");
237c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            }
238c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            viewName = classNode;
2393b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
240c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return viewName;
2413b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount    }
2423b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount
243c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private void parseData(File xml, XMLParser.ElementContext data,
244c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            ResourceBundle.LayoutFileBundle bundle) {
245c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (data == null) {
246c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return;
2473b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
248c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext imp : filter(data, "import")) {
249c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final Map<String, String> attrMap = attributeMap(imp);
250c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String type = attrMap.get("type");
251c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String alias = attrMap.get("alias");
252c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            Preconditions.check(StringUtils.isNotBlank(type), "Type of an import cannot be empty."
253c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    + " %s in %s", imp.toStringTree(), xml);
254c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            if (StringUtils.isEmpty(alias)) {
255c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                final String[] split = StringUtils.split(type, '.');
256c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                alias = split[split.length - 1];
257c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            }
258c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            bundle.addImport(alias, type, new Location(imp));
2593b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        }
2603bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
261c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext variable : filter(data, "variable")) {
262c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            final Map<String, String> attrMap = attributeMap(variable);
263c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String type = attrMap.get("type");
264c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String name = attrMap.get("name");
265c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            Preconditions.checkNotNull(type, "variable must have a type definition %s in %s",
266c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    variable.toStringTree(), xml);
267c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            Preconditions.checkNotNull(name, "variable must have a name %s in %s",
268c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    variable.toStringTree(), xml);
269c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            bundle.addVariable(name, type, new Location(variable));
270c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
271c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final String className = attributeMap(data).get("class");
272c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (StringUtils.isNotBlank(className)) {
273c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            bundle.setBindingClass(className);
274c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
2753bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
2763bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
277c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private XMLParser.ElementContext getDataNode(XMLParser.ElementContext root) {
278c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final List<XMLParser.ElementContext> data = filter(root, "data");
279c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (data.size() == 0) {
280c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return null;
281c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
282c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        Preconditions.check(data.size() == 1, "XML layout can have only 1 data tag");
283c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return data.get(0);
28495d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount    }
28595d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount
286c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private XMLParser.ElementContext getViewNode(File xml, XMLParser.ElementContext root) {
287c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final List<XMLParser.ElementContext> view = filterNot(root, "data");
288c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        Preconditions.check(view.size() == 1, "XML layout %s must have 1 view but has %s. root"
289c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                        + " children count %s", xml, view.size(), root.getChildCount());
290c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return view.get(0);
29196e1c821dd446d1ed78f8ae61131550588f60a24George Mount    }
29296e1c821dd446d1ed78f8ae61131550588f60a24George Mount
293c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private List<XMLParser.ElementContext> filter(XMLParser.ElementContext root,
294c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String name) {
295c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        List<XMLParser.ElementContext> result = new ArrayList<>();
296c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (root == null) {
297c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return result;
298c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
299c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final XMLParser.ContentContext content = root.content();
300c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (content == null) {
301c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return result;
302c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
303c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext child : XmlEditor.elements(root)) {
304c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            if (name.equals(child.elmName.getText())) {
305c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                result.add(child);
306c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            }
3073bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
3083bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return result;
3093bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
3103bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
311c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private List<XMLParser.ElementContext> filterNot(XMLParser.ElementContext root,
312c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            String name) {
313c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        List<XMLParser.ElementContext> result = new ArrayList<>();
314c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (root == null) {
315c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return result;
316c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
317c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final XMLParser.ContentContext content = root.content();
318c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (content == null) {
319c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return result;
320c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
321c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.ElementContext child : XmlEditor.elements(root)) {
322c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            if (!name.equals(child.elmName.getText())) {
323c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                result.add(child);
32495d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount            }
32595d1b38adeb5963ec5337e7dd6177b4bb5a03619George Mount        }
326c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return result;
3273bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
3283bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
329c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private boolean hasMergeInclude(XMLParser.ElementContext rootView) {
330c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return "merge".equals(rootView.elmName.getText()) && filter(rootView, "include").size() > 0;
3313bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
3323bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
3333bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    private File stripFileAndGetOriginal(File xml, String binderId)
3343bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            throws ParserConfigurationException, IOException, SAXException,
3353bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            XPathExpressionException {
3368e5d3b4aa4e47fc0150b4a26b58ec6e5c17b9d16George Mount        L.d("parsing resource file %s", xml.getAbsolutePath());
3373bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
3383bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        DocumentBuilder builder = factory.newDocumentBuilder();
3393bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        Document doc = builder.parse(xml);
3403bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        XPathFactory xPathFactory = XPathFactory.newInstance();
3413bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        XPath xPath = xPathFactory.newXPath();
3423bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final XPathExpression commentElementExpr = xPath
3433bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                .compile("//comment()[starts-with(., \" From: file:\")][last()]");
3443bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final NodeList commentElementNodes = (NodeList) commentElementExpr
3453bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar                .evaluate(doc, XPathConstants.NODESET);
3463bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("comment element nodes count %s", commentElementNodes.getLength());
3473bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (commentElementNodes.getLength() == 0) {
3483bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("cannot find comment element to find the actual file");
3493bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            return null;
3503bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
3513bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        final Node first = commentElementNodes.item(0);
35275da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        String actualFilePath = first.getNodeValue().substring(" From:".length()).trim();
3533bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        L.d("actual file to parse: %s", actualFilePath);
35475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        File actualFile = urlToFile(new java.net.URL(actualFilePath));
3553bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (!actualFile.canRead()) {
3563bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            L.d("cannot find original, skipping. %s", actualFile.getAbsolutePath());
3573bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            return null;
3583bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
3593bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar
3603bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        // now if file has any binding expressions, find and delete them
3613bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        // TODO we should rely on namespace to avoid parsing file twice
3623b8fedce6e9ee814ffe7b63d07aa5456f08fb9b2George Mount        boolean changed = isBindingLayout(doc, xPath);
3633bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        if (changed) {
3643bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar            stripBindingTags(xml, binderId);
3653bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        }
3663bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar        return actualFile;
3673bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar    }
36875da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar
369c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private boolean isBindingLayout(Document doc, XPath xPath) throws XPathExpressionException {
370c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return !get(doc, xPath, XPATH_BINDING_LAYOUT).isEmpty();
371c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
372c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
373c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private List<Node> get(Document doc, XPath xPath, String pattern)
374c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            throws XPathExpressionException {
375c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final XPathExpression expr = xPath.compile(pattern);
376c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET));
377c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
378c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
379c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private List<Node> toList(NodeList nodeList) {
380c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        List<Node> result = new ArrayList<Node>();
381c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (int i = 0; i < nodeList.getLength(); i++) {
382c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            result.add(nodeList.item(i));
383c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
384c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return result;
385c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
386c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
387c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private void stripBindingTags(File xml, String newTag) throws IOException {
388c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        String res = XmlEditor.strip(xml, newTag);
389c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (res != null) {
390c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            L.d("file %s has changed, overwriting %s", xml.getName(), xml.getAbsolutePath());
391c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            FileUtils.writeStringToFile(xml, res);
392c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
39375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    }
39475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar
39575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    public static File urlToFile(URL url) throws MalformedURLException {
39675da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        try {
39775da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            return new File(url.toURI());
398c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        } catch (IllegalArgumentException e) {
39975da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            MalformedURLException ex = new MalformedURLException(e.getLocalizedMessage());
40075da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            ex.initCause(e);
40175da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            throw ex;
402c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        } catch (URISyntaxException e) {
40375da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar            return new File(url.getPath());
40475da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar        }
40575da807698ae0f3b857d8160541cb03bb5ee1028Yigit Boyar    }
406c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
407c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private static Map<String, String> attributeMap(XMLParser.ElementContext root) {
408c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        final Map<String, String> result = new HashMap<>();
409c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        for (XMLParser.AttributeContext attr : XmlEditor.attributes(root)) {
410c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            result.put(escapeQuotes(attr.attrName.getText(), false),
411c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar                    escapeQuotes(attr.attrValue.getText(), true));
412c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
413c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        return result;
414c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
415c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar
416c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    private static String escapeQuotes(String textWithQuotes, boolean unescapeValue) {
417c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        char first = textWithQuotes.charAt(0);
418c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        int start = 0, end = textWithQuotes.length();
419c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (first == '"' || first == '\'') {
420c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            start = 1;
421c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
422c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        char last = textWithQuotes.charAt(textWithQuotes.length() - 1);
423c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (last == '"' || last == '\'') {
424c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            end -= 1;
425c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
426c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        String val = textWithQuotes.substring(start, end);
427c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        if (unescapeValue) {
428c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return StringEscapeUtils.unescapeXml(val);
429c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        } else {
430c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar            return val;
431c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar        }
432c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    }
4333bd87eef217d80a233677d7e267224d0ed3c2c55Yigit Boyar}
434