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