10cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar/*
20cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * Copyright (C) 2014 The Android Open Source Project
30cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar *
40cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
50cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * you may not use this file except in compliance with the License.
60cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * You may obtain a copy of the License at
70cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar *
80cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
90cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar *
100cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * Unless required by applicable law or agreed to in writing, software
110cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
120cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * See the License for the specific language governing permissions and
140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * limitations under the License.
150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar */
160cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
170cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarpackage android.databinding.tool.util;
180cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
19d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.BindingExpressionLexer;
20d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.BindingExpressionParser;
21d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.XMLLexer;
22d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.XMLParser;
23d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.XMLParser.AttributeContext;
24d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mountimport android.databinding.parser.XMLParser.ElementContext;
25d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount
264ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport com.google.common.base.Joiner;
274ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport com.google.common.xml.XmlEscapers;
284ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.ANTLRInputStream;
300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.CommonTokenStream;
310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.Token;
320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.tree.TerminalNode;
330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.io.FileUtils;
340cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.io.File;
36f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mountimport java.io.FileInputStream;
370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.io.IOException;
38f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mountimport java.io.InputStreamReader;
390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.util.ArrayList;
400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.util.Collections;
410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.util.Comparator;
420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.util.List;
430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar/**
450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * Ugly inefficient class to strip unwanted tags from XML.
460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar * Band-aid solution to unblock development
470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar */
480cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarpublic class XmlEditor {
490cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
50f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount    public static String strip(File f, String newTag, String encoding) throws IOException {
51f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount        FileInputStream fin = new FileInputStream(f);
52f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount        InputStreamReader reader = new InputStreamReader(fin, encoding);
53f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount        ANTLRInputStream inputStream = new ANTLRInputStream(reader);
540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLLexer lexer = new XMLLexer(inputStream);
550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLParser parser = new XMLParser(tokenStream);
570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLParser.DocumentContext expr = parser.document();
584ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        ElementContext root = expr.element();
590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (root == null || !"layout".equals(nodeName(root))) {
610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return null; // not a binding layout
620cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
630cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
640c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends ElementContext> childrenOfRoot = elements(root);
654ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        List<? extends ElementContext> dataNodes = filterNodesByName("data", childrenOfRoot);
660c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (dataNodes.size() > 1) {
670c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            L.e("Multiple binding data tags in %s. Expecting a maximum of one.",
680c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    f.getAbsolutePath());
690c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
719784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        ArrayList<String> lines = new ArrayList<String>();
720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        lines.addAll(FileUtils.readLines(f, "utf-8"));
730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
744ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        for (ElementContext it : dataNodes) {
750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            replace(lines, toPosition(it.getStart()), toEndPosition(it.getStop()), "");
760cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
774ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        List<? extends ElementContext> layoutNodes =
780c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                excludeNodesByName("data", childrenOfRoot);
790c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (layoutNodes.size() != 1) {
800c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            L.e("Only one layout element and one data element are allowed. %s has %d",
810c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    f.getAbsolutePath(), layoutNodes.size());
820c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
830cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
844ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        final ElementContext layoutNode = layoutNodes.get(0);
850cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
864ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        ArrayList<TagAndContext> noTag = new ArrayList<TagAndContext>();
870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        recurseReplace(layoutNode, lines, noTag, newTag, 0);
890cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
900cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        // Remove the <layout>
910cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position rootStartTag = toPosition(root.getStart());
920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position rootEndTag = toPosition(root.content().getStart());
930cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        replace(lines, rootStartTag, rootEndTag, "");
940cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
950cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        // Remove the </layout>
964ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        PositionPair endLayoutPositions = findTerminalPositions(root, lines);
970cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        replace(lines, endLayoutPositions.left, endLayoutPositions.right, "");
980cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
990cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        StringBuilder rootAttributes = new StringBuilder();
1000cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (AttributeContext attr : attributes(root)) {
1010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            rootAttributes.append(' ').append(attr.getText());
1020cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1034ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        TagAndContext noTagRoot = null;
1044ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        for (TagAndContext tagAndContext : noTag) {
1054ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            if (tagAndContext.getContext() == layoutNode) {
1064ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                noTagRoot = tagAndContext;
1070c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                break;
1080c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1090c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1100c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (noTagRoot != null) {
1114ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            TagAndContext newRootTag = new TagAndContext(
1124ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                    noTagRoot.getTag() + rootAttributes.toString(), layoutNode);
1130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            int index = noTag.indexOf(noTagRoot);
1140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            noTag.set(index, newRootTag);
1150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
1164ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            TagAndContext newRootTag =
1174ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                    new TagAndContext(rootAttributes.toString(), layoutNode);
1180cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            noTag.add(newRootTag);
1190cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        //noinspection NullableProblems
1214ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        Collections.sort(noTag, new Comparator<TagAndContext>() {
1220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            @Override
1234ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            public int compare(TagAndContext o1, TagAndContext o2) {
1244ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                Position start1 = toPosition(o1.getContext().getStart());
1254ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                Position start2 = toPosition(o2.getContext().getStart());
1269784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar                int lineCmp = start2.line - start1.line;
1270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (lineCmp != 0) {
1280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    return lineCmp;
1290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
1309784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar                return start2.charIndex - start1.charIndex;
1310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
1320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        });
1334ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        for (TagAndContext it : noTag) {
1344ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            ElementContext element = it.getContext();
1354ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            String tag = it.getTag();
1360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            Position endTagPosition = endTagPosition(element);
1370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            fixPosition(lines, endTagPosition);
1380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(endTagPosition.line);
1390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newLine = line.substring(0, endTagPosition.charIndex) + " " + tag +
1400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    line.substring(endTagPosition.charIndex);
1410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(endTagPosition.line, newLine);
1420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1434ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        return Joiner.on(StringUtils.LINE_SEPARATOR).join(lines);
1440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1460c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    private static <T extends XMLParser.ElementContext> List<T>
1470c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            filterNodesByName(String name, Iterable<T> items) {
1489784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        List<T> result = new ArrayList<T>();
1490c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (T item : items) {
1500c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (name.equals(nodeName(item))) {
1510c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(item);
1520c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1530c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1540c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
1550c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    }
1560c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar
1570c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    private static <T extends XMLParser.ElementContext> List<T>
1580c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            excludeNodesByName(String name, Iterable<T> items) {
1599784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        List<T> result = new ArrayList<T>();
1600c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (T item : items) {
1610c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (!name.equals(nodeName(item))) {
1620c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(item);
1630c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1640c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1650c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
1660c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    }
1670c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar
1680cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static Position toPosition(Token token) {
1690cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return new Position(token.getLine() - 1, token.getCharPositionInLine());
1700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static Position toEndPosition(Token token) {
1730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return new Position(token.getLine() - 1,
1740cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                token.getCharPositionInLine() + token.getText().length());
1750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1760cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1774ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    public static String nodeName(ElementContext elementContext) {
1780cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return elementContext.elmName.getText();
1790cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1800cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1814ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    public static List<? extends AttributeContext> attributes(ElementContext elementContext) {
1829784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        if (elementContext.attribute() == null)
1839784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar            return new ArrayList<AttributeContext>();
1849784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        else {
1850cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return elementContext.attribute();
1860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1894ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    public static List<? extends AttributeContext> expressionAttributes(
1904ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            ElementContext elementContext) {
1919784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        List<AttributeContext> result = new ArrayList<AttributeContext>();
1920c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (AttributeContext input : attributes(elementContext)) {
1930c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            String attrName = input.attrName.getText();
194d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount            boolean isExpression = attrName.equals("android:tag");
195d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount            if (!isExpression) {
196d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount                final String value = input.attrValue.getText();
197d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount                isExpression = isExpressionText(input.attrValue.getText());
198d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount            }
199d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount            if (isExpression) {
2000c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(input);
2010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
2020c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
2030c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
2040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
206d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount    private static boolean isExpressionText(String value) {
207d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        // Check if the expression ends with "}" and starts with "@{" or "@={", ignoring
208d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        // the surrounding quotes.
209d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        return (value.length() > 5 && value.charAt(value.length() - 2) == '}' &&
210d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount                ("@{".equals(value.substring(1, 3)) || "@={".equals(value.substring(1, 4))));
211d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount    }
212d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount
2134ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    private static Position endTagPosition(ElementContext context) {
2140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (context.content() == null) {
215f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount            // no content, so just choose the start of the "/>"
216f1081f6a15e6b905701bd3bbcb5d598731d05afbGeorge Mount            Position endTag = toPosition(context.getStop());
2170c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (endTag.charIndex <= 0) {
2180c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                L.e("invalid input in %s", context);
2190c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
2200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return endTag;
2210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
2220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            // tag with no attributes, but with content
2230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            Position position = toPosition(context.content().getStart());
2240c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (position.charIndex <= 0) {
2250c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                L.e("invalid input in %s", context);
2260c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
2270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            position.charIndex--;
2280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return position;
2290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2324ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    public static List<? extends ElementContext> elements(ElementContext context) {
2330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (context.content() != null && context.content().element() != null) {
2340cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return context.content().element();
2350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2369784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        return new ArrayList<ElementContext>();
2370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static boolean replace(ArrayList<String> lines, Position start, Position end,
2400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String text) {
2410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        fixPosition(lines, start);
2420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        fixPosition(lines, end);
2430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (start.line != end.line) {
2440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String startLine = lines.get(start.line);
2450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newStartLine = startLine.substring(0, start.charIndex) + text;
2460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, newStartLine);
2470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            for (int i = start.line + 1; i < end.line; i++) {
2480cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                String line = lines.get(i);
2490cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                lines.set(i, replaceWithSpaces(line, 0, line.length() - 1));
2500cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
2510cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String endLine = lines.get(end.line);
2520cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newEndLine = replaceWithSpaces(endLine, 0, end.charIndex - 1);
2530cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(end.line, newEndLine);
2540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return true;
2550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else if (end.charIndex - start.charIndex >= text.length()) {
2560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(start.line);
2570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            int endTextIndex = start.charIndex + text.length();
2580cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String replacedText = replaceRange(line, start.charIndex, endTextIndex, text);
2590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String spacedText = replaceWithSpaces(replacedText, endTextIndex, end.charIndex - 1);
2600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, spacedText);
2610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return true;
2620cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
2630cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(start.line);
2640cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newLine = replaceWithSpaces(line, start.charIndex, end.charIndex - 1);
2650cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, newLine);
2660cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return false;
2670cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2680cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2690cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String replaceRange(String line, int start, int end, String newText) {
2710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return line.substring(0, start) + newText + line.substring(end);
2720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2744ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    public static boolean hasExpressionAttributes(ElementContext context) {
2750c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends AttributeContext> expressions = expressionAttributes(context);
2760c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        int size = expressions.size();
27792a428505b9102bc0560d2d5be1768da097909c2George Mount        if (size == 0) {
27892a428505b9102bc0560d2d5be1768da097909c2George Mount            return false;
27992a428505b9102bc0560d2d5be1768da097909c2George Mount        } else if (size > 1) {
28092a428505b9102bc0560d2d5be1768da097909c2George Mount            return true;
28192a428505b9102bc0560d2d5be1768da097909c2George Mount        } else {
28292a428505b9102bc0560d2d5be1768da097909c2George Mount            // android:tag is included, regardless, so we must only count as an expression
28392a428505b9102bc0560d2d5be1768da097909c2George Mount            // if android:tag has a binding expression.
284d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount            return isExpressionText(expressions.get(0).attrValue.getText());
28592a428505b9102bc0560d2d5be1768da097909c2George Mount        }
2860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2884ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    private static int recurseReplace(ElementContext node, ArrayList<String> lines,
2894ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            ArrayList<TagAndContext> noTag,
2900cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newTag, int bindingIndex) {
2910cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int nextBindingIndex = bindingIndex;
2920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        boolean isMerge = "merge".equals(nodeName(node));
2930c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        final boolean containsInclude = filterNodesByName("include", elements(node)).size() > 0;
2942ac58b34e5200a34b0ba63884c375a68c9a84303George Mount        if (!isMerge && (hasExpressionAttributes(node) || newTag != null || containsInclude)) {
2950cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String tag = "";
2960cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (newTag != null) {
2970cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                tag = "android:tag=\"" + newTag + "_" + bindingIndex + "\"";
2980cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                nextBindingIndex++;
2990cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            } else if (!"include".equals(nodeName(node))) {
3000cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                tag = "android:tag=\"binding_" + bindingIndex + "\"";
3010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                nextBindingIndex++;
3020cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3030cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            for (AttributeContext it : expressionAttributes(node)) {
3040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position start = toPosition(it.getStart());
3050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position end = toEndPosition(it.getStop());
3060cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                String defaultVal = defaultReplacement(it);
3070cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (defaultVal != null) {
3080cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    replace(lines, start, end, it.attrName.getText() + "=\"" + defaultVal + "\"");
3090cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                } else if (replace(lines, start, end, tag)) {
3100cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    tag = "";
3110cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
3120cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (tag.length() != 0) {
3144ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                noTag.add(new TagAndContext(tag, node));
3150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3160cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3170cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3180cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String nextTag;
3190cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (bindingIndex == 0 && isMerge) {
3200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextTag = newTag;
3210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
3220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextTag = null;
3230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3244ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        for (ElementContext it : elements(node)) {
3250cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextBindingIndex = recurseReplace(it, lines, noTag, nextTag, nextBindingIndex);
3260cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return nextBindingIndex;
3280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String defaultReplacement(XMLParser.AttributeContext attr) {
3310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String textWithQuotes = attr.attrValue.getText();
3320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String escapedText = textWithQuotes.substring(1, textWithQuotes.length() - 1);
333d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        final boolean isTwoWay = escapedText.startsWith("@={");
334d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        final boolean isOneWay = escapedText.startsWith("@{");
335d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        if ((!isTwoWay && !isOneWay) || !escapedText.endsWith("}")) {
3360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return null;
3370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
338d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        final int startIndex = isTwoWay ? 3 : 2;
339d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        final int endIndex = escapedText.length() - 1;
340d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01George Mount        String text = StringUtils.unescapeXml(escapedText.substring(startIndex, endIndex));
3410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        ANTLRInputStream inputStream = new ANTLRInputStream(text);
3420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionLexer lexer = new BindingExpressionLexer(inputStream);
3430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
3440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser parser = new BindingExpressionParser(tokenStream);
3450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser.BindingSyntaxContext root = parser.bindingSyntax();
3460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser.DefaultsContext defaults = root.defaults();
3470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (defaults != null) {
3480cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            BindingExpressionParser.ConstantValueContext constantValue = defaults
3490cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    .constantValue();
3500cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            BindingExpressionParser.LiteralContext literal = constantValue.literal();
3510cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (literal != null) {
3520cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                BindingExpressionParser.StringLiteralContext stringLiteral = literal
3530cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        .stringLiteral();
3540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (stringLiteral != null) {
3550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    TerminalNode doubleQuote = stringLiteral.DoubleQuoteString();
3560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    if (doubleQuote != null) {
3570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String quotedStr = doubleQuote.getText();
3580cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unquoted = quotedStr.substring(1, quotedStr.length() - 1);
3594ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                        return XmlEscapers.xmlAttributeEscaper().escape(unquoted);
3600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    } else {
3610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String quotedStr = stringLiteral.SingleQuoteString().getText();
3620cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unquoted = quotedStr.substring(1, quotedStr.length() - 1);
3630cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unescaped = unquoted.replace("\"", "\\\"").replace("\\`", "`");
3644ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                        return XmlEscapers.xmlAttributeEscaper().escape(unescaped);
3650cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    }
3660cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
3670cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3680cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return constantValue.getText();
3690cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return null;
3710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3734ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    private static PositionPair findTerminalPositions(ElementContext node,
3744ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            ArrayList<String> lines) {
3750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position endPosition = toEndPosition(node.getStop());
3760cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position startPosition = toPosition(node.getStop());
3770cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int index;
3780cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        do {
3790cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            index = lines.get(startPosition.line).lastIndexOf("</");
3800cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            startPosition.line--;
3810cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } while (index < 0);
3820cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        startPosition.line++;
3830cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        startPosition.charIndex = index;
3840cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        //noinspection unchecked
3854ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        return new PositionPair(startPosition, endPosition);
3860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String replaceWithSpaces(String line, int start, int end) {
3890cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        StringBuilder lineBuilder = new StringBuilder(line);
3900cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (int i = start; i <= end; i++) {
3910cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lineBuilder.setCharAt(i, ' ');
3920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3930cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return lineBuilder.toString();
3940cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3950cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3960cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static void fixPosition(ArrayList<String> lines, Position pos) {
3970cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String line = lines.get(pos.line);
3980cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        while (pos.charIndex > line.length()) {
3990cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            pos.charIndex--;
4000cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
4010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
4020cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4030cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static class Position {
4040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int line;
4060cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int charIndex;
4070cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4080cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        public Position(int line, int charIndex) {
4090cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            this.line = line;
4100cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            this.charIndex = charIndex;
4110cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
4120cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
4130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4144ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    private static class TagAndContext {
4154ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private final String mTag;
4164ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private final ElementContext mElementContext;
4174ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
4184ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private TagAndContext(String tag, ElementContext elementContext) {
4194ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            mTag = tag;
4204ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            mElementContext = elementContext;
4214ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        }
4224ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
4234ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private ElementContext getContext() {
4244ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            return mElementContext;
4254ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        }
4264ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
4274ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private String getTag() {
4284ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            return mTag;
4294ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        }
4304ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    }
4314ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
4324ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    private static class PositionPair {
4334ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private final Position left;
4344ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private final Position right;
4354ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
4364ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        private PositionPair(Position left, Position right) {
4374ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            this.left = left;
4384ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta            this.right = right;
4394ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        }
4404ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta    }
4410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar}
442