XmlEditor.java revision 92a428505b9102bc0560d2d5be1768da097909c2
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
190cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.ANTLRInputStream;
200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.CommonTokenStream;
210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.Token;
220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.antlr.v4.runtime.tree.TerminalNode;
230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.io.FileUtils;
240cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.lang3.StringEscapeUtils;
250cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.lang3.StringUtils;
260cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.lang3.tuple.ImmutablePair;
270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport org.apache.commons.lang3.tuple.Pair;
280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport android.databinding.parser.BindingExpressionLexer;
300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport android.databinding.parser.BindingExpressionParser;
310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport android.databinding.parser.XMLLexer;
320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport android.databinding.parser.XMLParser;
330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport android.databinding.parser.XMLParser.AttributeContext;
342ac58b34e5200a34b0ba63884c375a68c9a84303George Mountimport android.databinding.parser.XMLParser.ElementContext;
350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.io.File;
370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.io.FileReader;
380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyarimport java.io.IOException;
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
500cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    public static String strip(File f, String newTag) throws IOException {
510cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        ANTLRInputStream inputStream = new ANTLRInputStream(new FileReader(f));
520cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLLexer lexer = new XMLLexer(inputStream);
530cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLParser parser = new XMLParser(tokenStream);
550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLParser.DocumentContext expr = parser.document();
560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        XMLParser.ElementContext root = expr.element();
570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
580cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (root == null || !"layout".equals(nodeName(root))) {
590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return null; // not a binding layout
600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
620c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends ElementContext> childrenOfRoot = elements(root);
630c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends XMLParser.ElementContext> dataNodes = filterNodesByName("data",
640c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                childrenOfRoot);
650c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (dataNodes.size() > 1) {
660c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            L.e("Multiple binding data tags in %s. Expecting a maximum of one.",
670c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    f.getAbsolutePath());
680c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
690cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
700c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        ArrayList<String> lines = new ArrayList<>();
710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        lines.addAll(FileUtils.readLines(f, "utf-8"));
720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (android.databinding.parser.XMLParser.ElementContext it : dataNodes) {
740cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            replace(lines, toPosition(it.getStart()), toEndPosition(it.getStop()), "");
750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
760c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends XMLParser.ElementContext> layoutNodes =
770c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                excludeNodesByName("data", childrenOfRoot);
780c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (layoutNodes.size() != 1) {
790c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            L.e("Only one layout element and one data element are allowed. %s has %d",
800c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    f.getAbsolutePath(), layoutNodes.size());
810c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
820cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
830c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        final XMLParser.ElementContext layoutNode = layoutNodes.get(0);
840cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
850c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        ArrayList<Pair<String, android.databinding.parser.XMLParser.ElementContext>> noTag =
860c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                new ArrayList<>();
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>
960cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        ImmutablePair<Position, Position> 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        }
1030c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        Pair<String, XMLParser.ElementContext> noTagRoot = null;
1040c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (Pair<String, XMLParser.ElementContext> pair : noTag) {
1050c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (pair.getRight() == layoutNode) {
1060c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                noTagRoot = pair;
1070c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                break;
1080c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1090c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1100c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        if (noTagRoot != null) {
1110cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            ImmutablePair<String, XMLParser.ElementContext>
1120cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    newRootTag = new ImmutablePair<>(
1130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    noTagRoot.getLeft() + rootAttributes.toString(), layoutNode);
1140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            int index = noTag.indexOf(noTagRoot);
1150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            noTag.set(index, newRootTag);
1160cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
1170cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            ImmutablePair<String, XMLParser.ElementContext> newRootTag =
1180cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    new ImmutablePair<>(rootAttributes.toString(), layoutNode);
1190cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            noTag.add(newRootTag);
1200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        //noinspection NullableProblems
1220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Collections.sort(noTag, new Comparator<Pair<String, XMLParser.ElementContext>>() {
1230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            @Override
1240cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            public int compare(Pair<String, XMLParser.ElementContext> o1,
1250cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    Pair<String, XMLParser.ElementContext> o2) {
1260cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position start1 = toPosition(o1.getRight().getStart());
1270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position start2 = toPosition(o2.getRight().getStart());
1280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                int lineCmp = Integer.compare(start2.line, start1.line);
1290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (lineCmp != 0) {
1300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    return lineCmp;
1310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
1320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                return Integer.compare(start2.charIndex, start1.charIndex);
1330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
1340cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        });
1350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (Pair<String, android.databinding.parser.XMLParser.ElementContext> it : noTag) {
1360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            XMLParser.ElementContext element = it.getRight();
1370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String tag = it.getLeft();
1380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            Position endTagPosition = endTagPosition(element);
1390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            fixPosition(lines, endTagPosition);
1400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(endTagPosition.line);
1410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newLine = line.substring(0, endTagPosition.charIndex) + " " + tag +
1420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    line.substring(endTagPosition.charIndex);
1430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(endTagPosition.line, newLine);
1440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return StringUtils.join(lines, System.getProperty("line.separator"));
1460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1480c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    private static <T extends XMLParser.ElementContext> List<T>
1490c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            filterNodesByName(String name, Iterable<T> items) {
1500c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<T> result = new ArrayList<>();
1510c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (T item : items) {
1520c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (name.equals(nodeName(item))) {
1530c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(item);
1540c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1550c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1560c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
1570c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    }
1580c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar
1590c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    private static <T extends XMLParser.ElementContext> List<T>
1600c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            excludeNodesByName(String name, Iterable<T> items) {
1610c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<T> result = new ArrayList<>();
1620c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (T item : items) {
1630c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (!name.equals(nodeName(item))) {
1640c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(item);
1650c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
1660c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
1670c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
1680c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar    }
1690c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar
1700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static Position toPosition(Token token) {
1710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return new Position(token.getLine() - 1, token.getCharPositionInLine());
1720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
1740cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static Position toEndPosition(Token token) {
1750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return new Position(token.getLine() - 1,
1760cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                token.getCharPositionInLine() + token.getText().length());
1770cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1780cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
179c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    public static String nodeName(XMLParser.ElementContext elementContext) {
1800cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return elementContext.elmName.getText();
1810cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1820cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
183c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    public static List<? extends AttributeContext> attributes(XMLParser.ElementContext elementContext) {
1840cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (elementContext.attribute() == null) {
1850c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            return new ArrayList<>();
1860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
1870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return elementContext.attribute();
1880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
1890cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
1900cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
191c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    public static List<? extends AttributeContext> expressionAttributes (
1920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            XMLParser.ElementContext elementContext) {
1930c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<AttributeContext> result = new ArrayList<>();
1940c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        for (AttributeContext input : attributes(elementContext)) {
1950c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            String attrName = input.attrName.getText();
1960c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            String value = input.attrValue.getText();
1970c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (attrName.equals("android:tag") ||
1980c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    (value.startsWith("\"@{") && value.endsWith("}\"")) ||
1990c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                    (value.startsWith("'@{") && value.endsWith("}'"))) {
2000c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                result.add(input);
2010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
2020c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        }
2030c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return result;
2040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2060cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static Position endTagPosition(XMLParser.ElementContext context) {
2070cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (context.content() == null) {
2080cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            // no content, so just subtract from the "/>"
2090cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            Position endTag = toEndPosition(context.getStop());
2100c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (endTag.charIndex <= 0) {
2110c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                L.e("invalid input in %s", context);
2120c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
2130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            endTag.charIndex -= 2;
2140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return endTag;
2150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
2160cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            // tag with no attributes, but with content
2170cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            Position position = toPosition(context.content().getStart());
2180c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            if (position.charIndex <= 0) {
2190c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar                L.e("invalid input in %s", context);
2200c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar            }
2210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            position.charIndex--;
2220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return position;
2230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2240cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2250cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
226c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    public static List<? extends android.databinding.parser.XMLParser.ElementContext> elements(
2270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            XMLParser.ElementContext context) {
2280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (context.content() != null && context.content().element() != null) {
2290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return context.content().element();
2300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2310c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        return new ArrayList<>();
2320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2340cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static boolean replace(ArrayList<String> lines, Position start, Position end,
2350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String text) {
2360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        fixPosition(lines, start);
2370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        fixPosition(lines, end);
2380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (start.line != end.line) {
2390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String startLine = lines.get(start.line);
2400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newStartLine = startLine.substring(0, start.charIndex) + text;
2410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, newStartLine);
2420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            for (int i = start.line + 1; i < end.line; i++) {
2430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                String line = lines.get(i);
2440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                lines.set(i, replaceWithSpaces(line, 0, line.length() - 1));
2450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
2460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String endLine = lines.get(end.line);
2470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newEndLine = replaceWithSpaces(endLine, 0, end.charIndex - 1);
2480cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(end.line, newEndLine);
2490cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return true;
2500cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else if (end.charIndex - start.charIndex >= text.length()) {
2510cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(start.line);
2520cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            int endTextIndex = start.charIndex + text.length();
2530cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String replacedText = replaceRange(line, start.charIndex, endTextIndex, text);
2540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String spacedText = replaceWithSpaces(replacedText, endTextIndex, end.charIndex - 1);
2550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, spacedText);
2560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return true;
2570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
2580cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String line = lines.get(start.line);
2590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newLine = replaceWithSpaces(line, start.charIndex, end.charIndex - 1);
2600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lines.set(start.line, newLine);
2610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return false;
2620cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
2630cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2640cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2650cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String replaceRange(String line, int start, int end, String newText) {
2660cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return line.substring(0, start) + newText + line.substring(end);
2670cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2680cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
269c1560e6b00b398867da12fbdc5a1fcd1d50b801cYigit Boyar    public static boolean hasExpressionAttributes(XMLParser.ElementContext context) {
2700c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        List<? extends AttributeContext> expressions = expressionAttributes(context);
2710c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        int size = expressions.size();
27292a428505b9102bc0560d2d5be1768da097909c2George Mount        if (size == 0) {
27392a428505b9102bc0560d2d5be1768da097909c2George Mount            return false;
27492a428505b9102bc0560d2d5be1768da097909c2George Mount        } else if (size > 1) {
27592a428505b9102bc0560d2d5be1768da097909c2George Mount            return true;
27692a428505b9102bc0560d2d5be1768da097909c2George Mount        } else {
27792a428505b9102bc0560d2d5be1768da097909c2George Mount            // android:tag is included, regardless, so we must only count as an expression
27892a428505b9102bc0560d2d5be1768da097909c2George Mount            // if android:tag has a binding expression.
27992a428505b9102bc0560d2d5be1768da097909c2George Mount            String value = expressions.get(0).attrValue.getText();
28092a428505b9102bc0560d2d5be1768da097909c2George Mount            return value.startsWith("\"@{") || value.startsWith("'@{");
28192a428505b9102bc0560d2d5be1768da097909c2George Mount        }
2820cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
2830cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
2840cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static int recurseReplace(XMLParser.ElementContext node, ArrayList<String> lines,
2850cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            ArrayList<Pair<String, XMLParser.ElementContext>> noTag,
2860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String newTag, int bindingIndex) {
2870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int nextBindingIndex = bindingIndex;
2880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        boolean isMerge = "merge".equals(nodeName(node));
2890c2ed0cbaee2f206e926bfc780b05e9f1e52b551Yigit Boyar        final boolean containsInclude = filterNodesByName("include", elements(node)).size() > 0;
2902ac58b34e5200a34b0ba63884c375a68c9a84303George Mount        if (!isMerge && (hasExpressionAttributes(node) || newTag != null || containsInclude)) {
2910cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            String tag = "";
2920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (newTag != null) {
2930cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                tag = "android:tag=\"" + newTag + "_" + bindingIndex + "\"";
2940cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                nextBindingIndex++;
2950cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            } else if (!"include".equals(nodeName(node))) {
2960cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                tag = "android:tag=\"binding_" + bindingIndex + "\"";
2970cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                nextBindingIndex++;
2980cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
2990cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            for (AttributeContext it : expressionAttributes(node)) {
3000cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position start = toPosition(it.getStart());
3010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                Position end = toEndPosition(it.getStop());
3020cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                String defaultVal = defaultReplacement(it);
3030cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (defaultVal != null) {
3040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    replace(lines, start, end, it.attrName.getText() + "=\"" + defaultVal + "\"");
3050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                } else if (replace(lines, start, end, tag)) {
3060cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    tag = "";
3070cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
3080cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3090cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (tag.length() != 0) {
3100cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                noTag.add(new ImmutablePair<>(tag, node));
3110cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3120cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3130cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3140cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String nextTag;
3150cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (bindingIndex == 0 && isMerge) {
3160cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextTag = newTag;
3170cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } else {
3180cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextTag = null;
3190cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3200cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (XMLParser.ElementContext it : elements(node)) {
3210cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            nextBindingIndex = recurseReplace(it, lines, noTag, nextTag, nextBindingIndex);
3220cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3230cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return nextBindingIndex;
3240cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3250cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3260cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String defaultReplacement(XMLParser.AttributeContext attr) {
3270cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String textWithQuotes = attr.attrValue.getText();
3280cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String escapedText = textWithQuotes.substring(1, textWithQuotes.length() - 1);
3290cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (!escapedText.startsWith("@{") || !escapedText.endsWith("}")) {
3300cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return null;
3310cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3320cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String text = StringEscapeUtils
3330cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                .unescapeXml(escapedText.substring(2, escapedText.length() - 1));
3340cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        ANTLRInputStream inputStream = new ANTLRInputStream(text);
3350cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionLexer lexer = new BindingExpressionLexer(inputStream);
3360cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
3370cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser parser = new BindingExpressionParser(tokenStream);
3380cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser.BindingSyntaxContext root = parser.bindingSyntax();
3390cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        BindingExpressionParser.DefaultsContext defaults = root.defaults();
3400cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        if (defaults != null) {
3410cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            BindingExpressionParser.ConstantValueContext constantValue = defaults
3420cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    .constantValue();
3430cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            BindingExpressionParser.LiteralContext literal = constantValue.literal();
3440cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            if (literal != null) {
3450cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                BindingExpressionParser.StringLiteralContext stringLiteral = literal
3460cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        .stringLiteral();
3470cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                if (stringLiteral != null) {
3480cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    TerminalNode doubleQuote = stringLiteral.DoubleQuoteString();
3490cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    if (doubleQuote != null) {
3500cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String quotedStr = doubleQuote.getText();
3510cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unquoted = quotedStr.substring(1, quotedStr.length() - 1);
3520cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        return StringEscapeUtils.escapeXml10(unquoted);
3530cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    } else {
3540cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String quotedStr = stringLiteral.SingleQuoteString().getText();
3550cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unquoted = quotedStr.substring(1, quotedStr.length() - 1);
3560cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        String unescaped = unquoted.replace("\"", "\\\"").replace("\\`", "`");
3570cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                        return StringEscapeUtils.escapeXml10(unescaped);
3580cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                    }
3590cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar                }
3600cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            }
3610cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            return constantValue.getText();
3620cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3630cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return null;
3640cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3650cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3660cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static ImmutablePair<Position, Position> findTerminalPositions(
3670cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            XMLParser.ElementContext node,  ArrayList<String> lines) {
3680cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position endPosition = toEndPosition(node.getStop());
3690cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        Position startPosition = toPosition(node.getStop());
3700cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int index;
3710cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        do {
3720cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            index = lines.get(startPosition.line).lastIndexOf("</");
3730cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            startPosition.line--;
3740cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        } while (index < 0);
3750cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        startPosition.line++;
3760cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        startPosition.charIndex = index;
3770cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        //noinspection unchecked
3780cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return new ImmutablePair<>(startPosition, endPosition);
3790cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3800cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3810cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static String replaceWithSpaces(String line, int start, int end) {
3820cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        StringBuilder lineBuilder = new StringBuilder(line);
3830cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        for (int i = start; i <= end; i++) {
3840cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            lineBuilder.setCharAt(i, ' ');
3850cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3860cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        return lineBuilder.toString();
3870cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3880cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3890cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static void fixPosition(ArrayList<String> lines, Position pos) {
3900cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        String line = lines.get(pos.line);
3910cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        while (pos.charIndex > line.length()) {
3920cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            pos.charIndex--;
3930cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
3940cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
3950cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3960cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    private static class Position {
3970cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
3980cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int line;
3990cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        int charIndex;
4000cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4010cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        public Position(int line, int charIndex) {
4020cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            this.line = line;
4030cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar            this.charIndex = charIndex;
4040cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar        }
4050cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar    }
4060cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar
4070cb9fbb96197af013f4f879ed6cddf2681b88fd6Yigit Boyar}
408