/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.common.xml;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.testutils.TestUtils;
import com.android.utils.XmlUtils;
import com.google.common.base.Charsets;
import com.google.common.io.Files;

import junit.framework.TestCase;

import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.security.Permission;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

@SuppressWarnings("javadoc")
public class XmlPrettyPrinterTest {



    private void checkFormat(XmlFormatPreferences prefs,
            String xml,
            String expected, String delimiter, String startNodeName,
            String endNodeName) throws Exception {

        Document document = XmlUtils.parseDocumentSilently(xml, true);
        assertNotNull(document);
        XmlFormatStyle style = XmlFormatStyle.get(document);

        XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, delimiter);

        StringBuilder sb = new StringBuilder(1000);
        Node startNode = document;
        Node endNode = document;
        if (startNodeName != null) {
            startNode = findNode(document.getDocumentElement(), startNodeName);
        }
        if (endNodeName != null) {
            endNode = findNode(document.getDocumentElement(), endNodeName);
        }

        printer.prettyPrint(-1, document, startNode, endNode, sb, false/*openTagOnly*/);
        String formatted = sb.toString();
        if (!expected.equals(formatted)) {
            System.out.println(formatted);
        }
        assertEquals(expected, formatted);
    }

    private static Node findNode(Node node, String nodeName) {
        if (node.getNodeName().equals(nodeName)) {
            return node;
        }

        NodeList children = node.getChildNodes();
        for (int i = 0, n = children.getLength(); i < n; i++) {
            Node child = children.item(i);
            Node result = findNode(child, nodeName);
            if (result != null) {
                return result;
            }
        }

        return null;
    }

    private void checkFormat(XmlFormatPreferences prefs, String xml,
            String expected, String delimiter) throws Exception {
        checkFormat(prefs, xml, expected, delimiter, null, null);
    }

    private void checkFormat(XmlFormatPreferences prefs, String xml,
            String expected) throws Exception {
        checkFormat(prefs, xml, expected, "\n"); //$NON-NLS-1$
    }
    private void checkFormat(String xml, String expected)
            throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        checkFormat(prefs, xml, expected);
    }

    @Test
    public void testLayout1() throws Exception {
        checkFormat(
                "<LinearLayout><Button></Button></LinearLayout>",

                "<LinearLayout>\n" +
                "\n" +
                "    <Button />\n" +
                "\n" +
                "</LinearLayout>");
    }

    @Test
    public void testLayout2() throws Exception {
        checkFormat(
                "<LinearLayout><Button foo=\"bar\"></Button></LinearLayout>",

                "<LinearLayout>\n" +
                "\n" +
                "    <Button foo=\"bar\" />\n" +
                "\n" +
                "</LinearLayout>");
    }

    @Test
    public void testLayout3() throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        prefs.oneAttributeOnFirstLine = true;
        checkFormat(
                prefs,
                "<LinearLayout><Button foo=\"bar\"></Button></LinearLayout>",

                "<LinearLayout>\n" +
                "\n" +
                "    <Button foo=\"bar\" />\n" +
                "\n" +
                "</LinearLayout>");
    }

    @Test
    public void testClosedElements() throws Exception {
        checkFormat(
                "<resources>\n" +
                "<item   name=\"title_container\"  type=\"id\"   />\n" +
                "<item name=\"title_logo\" type=\"id\"/>\n" +
                "</resources>\n",

                "<resources>\n" +
                "\n" +
                "    <item name=\"title_container\" type=\"id\"/>\n" +
                "    <item name=\"title_logo\" type=\"id\"/>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testResources() throws Exception {
        checkFormat(
                "<resources><item name=\"foo\">Text value here </item></resources>",
                "<resources>\n\n" +
                "    <item name=\"foo\">Text value here </item>\n" +
                "\n</resources>");
    }

    @Test
    public void testNodeTypes() throws Exception {
        // Ensures that a document with all kinds of node types is serialized correctly
        checkFormat(

                "<!--\n" +
                "/**\n" +
                " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
                " */\n" +
                "-->\n" +
                "<!DOCTYPE metadata [\n" +
                "<!ELEMENT metadata (category)*>\n" +
                "<!ENTITY % ISOLat2\n" +
                "         SYSTEM \"http://www.xml.com/iso/isolat2-xml.entities\" >\n" +
                "]>\n" +
                "<LinearLayout\n" +
                "    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
                "    android:orientation=\"vertical\">\n" +
                "<![CDATA[\n" +
                "This is character data!\n" +
                "<!-- This is not a comment! -->\n" +
                "and <this is not an element>\n" +
                "]]>         \n" +
                "This is text: &lt; and &amp;\n" +
                "<!-- comment 1 \"test\"... -->\n" +
                "<!-- ... comment2 -->\n" +
                "%ISOLat2;        \n" +
                "<!-- \n" +
                "Type <key>less-than</key> (&#x3C;)\n" +
                "-->        \n" +
                "</LinearLayout>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<!--\n" +
                "/**\n" +
                " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
                " */\n" +
                "-->\n" +
                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
                "    android:orientation=\"vertical\" >\n" +
                "<![CDATA[\n" +
                "This is character data!\n" +
                "<!-- This is not a comment! -->\n" +
                "and <this is not an element>\n" +
                "]]>\n" +
                "This is text: &lt; and &amp;\n" +
                "    <!-- comment 1 \"test\"... -->\n" +
                "    <!-- ... comment2 -->\n" +
                "%ISOLat2;        \n" +
                "<!-- Type <key>less-than</key> (&#x3C;) -->\n" +
                "\n" +
                "</LinearLayout>");
    }

    @Test
    public void testWindowsDelimiters() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<LinearLayout><Button foo=\"bar\"></Button></LinearLayout>",

                "<LinearLayout>\r\n" +
                "\r\n" +
                "    <Button foo=\"bar\" />\r\n" +
                "\r\n" +
                "</LinearLayout>",
                "\r\n");
    }

    @Test
    public void testRemoveBlanklines() throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        prefs.removeEmptyLines = true;
        checkFormat(
                prefs,
                "<foo><bar><baz1></baz1><baz2></baz2></bar><bar2></bar2><bar3><baz12></baz12></bar3></foo>",

                ""
                + "<foo>\n"
                + "    <bar>\n"
                + "        <baz1 />\n"
                + "        <baz2 />\n"
                + "    </bar>\n"
                + "    <bar2 />\n"
                + "    <bar3>\n"
                + "        <baz12 />\n"
                + "    </bar3>\n"
                + "</foo>");
    }

    @Test
    public void testRange() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<LinearLayout><Button foo=\"bar\"></Button><CheckBox/></LinearLayout>",
                "\n" +
                "    <Button foo=\"bar\" />\n" +
                "\n" +
                "    <CheckBox />\n",
                "\n",
                "Button", "CheckBox");
    }

    @Test
    public void testOpenTagOnly() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<LinearLayout><Button foo=\"bar\"><Foo/></Button><CheckBox/></LinearLayout>",
                "\n" +
                "    <Button foo=\"bar\" >\n" +
                "\n" +
                "        <Foo />\n" +
                "    </Button>\n",
                "\n",

                "Button", "Button");
    }

    @Test
    public void testRange2() throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        prefs.removeEmptyLines = true;
        checkFormat(
                prefs,
                "<foo><bar><baz1></baz1><baz2></baz2></bar><bar2></bar2><bar3><baz12></baz12></bar3></foo>",

                "        <baz1 />\n" +
                "        <baz2 />\n" +
                "    </bar>\n" +
                "    <bar2 />\n" +
                "    <bar3>\n" +
                "        <baz12 />\n",

                "\n",
                "baz1", "baz12");
    }

    @Test
    public void testEOLcomments() throws Exception {
        checkFormat(
                "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
                "    <item android:state_pressed=\"true\"\n" +
                "     android:color=\"#ffff0000\"/> <!-- pressed -->\n" +
                "    <item android:state_focused=\"true\"\n" +
                "     android:color=\"#ff0000ff\"/> <!-- focused -->\n" +
                "    <item android:color=\"#ff000000\"/> <!-- default -->\n" +
                "</selector>",

                ""
                + "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
                + "\n"
                + "    <item\n"
                + "        android:state_pressed=\"true\"\n"
                + "        android:color=\"#ffff0000\"/> <!-- pressed -->\n"
                + "    <item\n"
                + "        android:state_focused=\"true\"\n"
                + "        android:color=\"#ff0000ff\"/> <!-- focused -->\n"
                + "    <item android:color=\"#ff000000\"/> <!-- default -->\n"
                + "\n"
                + "</selector>");
    }

    @Test
    public void testFormatColorList() throws Exception {
        checkFormat(
                "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
                "<item android:state_activated=\"true\" android:color=\"#FFFFFF\"/>\n" +
                "<item android:color=\"#777777\" /> <!-- not selected -->\n" +
                "</selector>",

                ""
                + "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
                + "\n"
                + "    <item\n"
                + "        android:state_activated=\"true\"\n"
                + "        android:color=\"#FFFFFF\"/>\n"
                + "    <item android:color=\"#777777\"/> <!-- not selected -->\n"
                + "\n"
                + "</selector>");
    }

    @Test
    public void testPreserveNewlineAfterComment() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources><dimen name=\"colorstrip_height\">6dip</dimen>\n" +
                "    <!-- comment1 --><dimen name=\"title_height\">45dip</dimen>\n" +
                "\n" +
                "    <!-- comment2: newline above --><dimen name=\"now_playing_height\">90dip</dimen>\n" +
                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
                "\n" +
                "\n" +
                "    <!-- comment3: newline above and below -->\n" +
                "\n" +
                "\n" +
                "\n" +
                "    <dimen name=\"text_size_medium\">18sp</dimen><dimen name=\"text_size_large\">22sp</dimen>\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
                "    <!-- comment1 -->\n" +
                "    <dimen name=\"title_height\">45dip</dimen>\n" +
                "\n" +
                "    <!-- comment2: newline above -->\n" +
                "    <dimen name=\"now_playing_height\">90dip</dimen>\n" +
                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
                "\n" +
                "    <!-- comment3: newline above and below -->\n" +
                "\n" +
                "    <dimen name=\"text_size_medium\">18sp</dimen>\n" +
                "    <dimen name=\"text_size_large\">22sp</dimen>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testPlurals() throws Exception {
        checkFormat(
                "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" +
                "<string name=\"toast_sync_error\">Sync error: <xliff:g id=\"error\">%1$s</xliff:g></string>\n" +
                "<string name=\"session_subtitle\"><xliff:g id=\"time\">%1$s</xliff:g> in <xliff:g id=\"room\">%2$s</xliff:g></string>\n" +
                "<plurals name=\"now_playing_countdown\">\n" +
                "<item quantity=\"zero\"><xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "<item quantity=\"one\"><xliff:g id=\"number_of_days\">%1$s</xliff:g> day, <xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "<item quantity=\"other\"><xliff:g id=\"number_of_days\">%1$s</xliff:g> days, <xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "</plurals>\n" +
                "</resources>",

                "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" +
                "\n" +
                "    <string name=\"toast_sync_error\">Sync error: <xliff:g id=\"error\">%1$s</xliff:g></string>\n" +
                "    <string name=\"session_subtitle\"><xliff:g id=\"time\">%1$s</xliff:g> in <xliff:g id=\"room\">%2$s</xliff:g></string>\n" +
                "\n" +
                "    <plurals name=\"now_playing_countdown\">\n" +
                "        <item quantity=\"zero\"><xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "        <item quantity=\"one\"><xliff:g id=\"number_of_days\">%1$s</xliff:g> day, <xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "        <item quantity=\"other\"><xliff:g id=\"number_of_days\">%1$s</xliff:g> days, <xliff:g id=\"remaining_time\">%2$s</xliff:g></item>\n" +
                "    </plurals>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testMultiAttributeResource() throws Exception {
        checkFormat(
                "<resources><string name=\"debug_enable_debug_logging_label\" translatable=\"false\">Enable extra debug logging?</string></resources>",

                "<resources>\n" +
                "\n" +
                "    <string name=\"debug_enable_debug_logging_label\" translatable=\"false\">Enable extra debug logging?</string>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testMultilineCommentAlignment() throws Exception {
        checkFormat(
                "<resources>" +
                "    <!-- Deprecated strings - Move the identifiers to this section, mark as DO NOT TRANSLATE,\n" +
                "         and remove the actual text.  These will be removed in a bulk operation. -->\n" +
                "    <!-- Do Not Translate.  Unused string. -->\n" +
                "    <string name=\"meeting_invitation\"></string>\n" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "    <!--\n" +
                "         Deprecated strings - Move the identifiers to this section, mark as DO NOT TRANSLATE,\n" +
                "         and remove the actual text.  These will be removed in a bulk operation.\n" +
                "    -->\n" +
                "    <!-- Do Not Translate.  Unused string. -->\n" +
                "    <string name=\"meeting_invitation\"></string>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testLineCommentSpacing() throws Exception {
        checkFormat(
                "<resources>\n" +
                "\n" +
                "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
                "    <!-- comment1 -->\n" +
                "    <dimen name=\"title_height\">45dip</dimen>\n" +
                "    <!-- comment2: no newlines -->\n" +
                "    <dimen name=\"now_playing_height\">90dip</dimen>\n" +
                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
                "\n" +
                "    <!-- comment3: newline above and below -->\n" +
                "\n" +
                "    <dimen name=\"text_size_medium\">18sp</dimen>\n" +
                "    <dimen name=\"text_size_large\">22sp</dimen>\n" +
                "\n" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
                "    <!-- comment1 -->\n" +
                "    <dimen name=\"title_height\">45dip</dimen>\n" +
                "    <!-- comment2: no newlines -->\n" +
                "    <dimen name=\"now_playing_height\">90dip</dimen>\n" +
                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
                "\n" +
                "    <!-- comment3: newline above and below -->\n" +
                "\n" +
                "    <dimen name=\"text_size_medium\">18sp</dimen>\n" +
                "    <dimen name=\"text_size_large\">22sp</dimen>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testCommentHandling() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<foo >\n" +
                "\n" +
                "    <!-- abc\n" +
                "         def\n" +
                "         ghi -->\n" +
                "\n" +
                "    <!-- abc\n" +
                "    def\n" +
                "    ghi -->\n" +
                "    \n" +
                "<!-- abc\n" +
                "def\n" +
                "ghi -->\n" +
                "\n" +
                "</foo>",

                "<foo>\n" +
                "\n" +
                "    <!--\n" +
                "         abc\n" +
                "         def\n" +
                "         ghi\n" +
                "    -->\n" +
                "\n" +
                "\n" +
                "    <!--\n" +
                "    abc\n" +
                "    def\n" +
                "    ghi\n" +
                "    -->\n" +
                "\n" +
                "\n" +
                "    <!--\n" +
                "abc\n" +
                "def\n" +
                "ghi\n" +
                "    -->\n" +
                "\n" +
                "</foo>");
    }

    @Test
    public void testCommentHandling2() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<foo >\n" +
                "    <!-- multi -->\n" +
                "\n" +
                "    <bar />\n" +
                "</foo>",

                "<foo>\n" +
                "\n" +
                "    <!-- multi -->\n" +
                "\n" +
                "    <bar />\n" +
                "\n" +
                "</foo>");
    }

    @Test
    public void testMenus1() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                // http://code.google.com/p/android/issues/detail?id=21383
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n" +
                "\n" +
                "    <item\n" +
                "        android:id=\"@+id/menu_debug\"\n" +
                "        android:icon=\"@android:drawable/ic_menu_more\"\n" +
                "        android:showAsAction=\"ifRoom|withText\"\n" +
                "        android:title=\"@string/menu_debug\">\n" +
                "    \n" +
                "        <menu>\n" +
                "                <item\n" +
                "                    android:id=\"@+id/menu_debug_clearCache_memory\"\n" +
                "                    android:icon=\"@android:drawable/ic_menu_delete\"\n" +
                "                    android:showAsAction=\"ifRoom|withText\"\n" +
                "                    android:title=\"@string/menu_debug_clearCache_memory\"/>\n" +
                "    \n" +
                "                <item\n" +
                "                    android:id=\"@+id/menu_debug_clearCache_file\"\n" +
                "                    android:icon=\"@android:drawable/ic_menu_delete\"\n" +
                "                    android:showAsAction=\"ifRoom|withText\"\n" +
                "                    android:title=\"@string/menu_debug_clearCache_file\"/>\n" +
                "        </menu>\n" +
                "    </item>\n" +
                "</menu>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n" +
                "\n" +
                "    <item\n" +
                "        android:id=\"@+id/menu_debug\"\n" +
                "        android:icon=\"@android:drawable/ic_menu_more\"\n" +
                "        android:showAsAction=\"ifRoom|withText\"\n" +
                "        android:title=\"@string/menu_debug\">\n" +
                "        <menu>\n" +
                "            <item\n" +
                "                android:id=\"@+id/menu_debug_clearCache_memory\"\n" +
                "                android:icon=\"@android:drawable/ic_menu_delete\"\n" +
                "                android:showAsAction=\"ifRoom|withText\"\n" +
                "                android:title=\"@string/menu_debug_clearCache_memory\"/>\n" +
                "            <item\n" +
                "                android:id=\"@+id/menu_debug_clearCache_file\"\n" +
                "                android:icon=\"@android:drawable/ic_menu_delete\"\n" +
                "                android:showAsAction=\"ifRoom|withText\"\n" +
                "                android:title=\"@string/menu_debug_clearCache_file\"/>\n" +
                "        </menu>\n" +
                "    </item>\n" +
                "\n" +
                "</menu>");
    }

    @Test
    public void testMenus2() throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        prefs.removeEmptyLines = true;
        checkFormat(
                prefs,
                // http://code.google.com/p/android/issues/detail?id=21346
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
                "  <item>\n" +
                "    <shape android:shape=\"rectangle\">\n" +
                "      <stroke\n" +
                "        android:width=\"1dip\"\n" +
                "        android:color=\"@color/line_separator\"/>\n" +
                "      <solid android:color=\"@color/event_header_background\"/>\n" +
                "    </shape>\n" +
                "  </item>\n" +
                "  <item\n" +
                "    android:bottom=\"1dip\"\n" +
                "    android:top=\"1dip\">\n" +
                "    <shape android:shape=\"rectangle\">\n" +
                "      <stroke\n" +
                "        android:width=\"1dip\"\n" +
                "        android:color=\"@color/event_header_background\"/>\n" +
                "      <solid android:color=\"@color/transparent\"/>\n" +
                "    </shape>\n" +
                "  </item>\n" +
                "</layer-list>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n" +
                "    <item>\n" +
                "        <shape android:shape=\"rectangle\" >\n" +
                "            <stroke\n" +
                "                android:width=\"1dip\"\n" +
                "                android:color=\"@color/line_separator\" />\n" +
                "            <solid android:color=\"@color/event_header_background\" />\n" +
                "        </shape>\n" +
                "    </item>\n" +
                "    <item\n" +
                "        android:bottom=\"1dip\"\n" +
                "        android:top=\"1dip\">\n" +
                "        <shape android:shape=\"rectangle\" >\n" +
                "            <stroke\n" +
                "                android:width=\"1dip\"\n" +
                "                android:color=\"@color/event_header_background\" />\n" +
                "            <solid android:color=\"@color/transparent\" />\n" +
                "        </shape>\n" +
                "    </item>\n" +
                "</layer-list>");
    }

    @Test
    public void testMenus3() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                // http://code.google.com/p/android/issues/detail?id=21227
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n" +
                "\n" +
                "    <item\n" +
                "        android:icon=\"@android:drawable/ic_menu_more\"\n" +
                "        android:title=\"@string/account_list_menu_more\">\n" +
                "        <menu>\n" +
                "            <item\n" +
                "                android:id=\"@+id/account_list_menu_backup_restore\"\n" +
                "                android:icon=\"@android:drawable/ic_menu_save\"\n" +
                "                android:title=\"@string/account_list_menu_backup_restore\"/>\n" +
                "        </menu>\n" +
                "    </item>\n" +
                "\n" +
                "</menu>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n" +
                "\n" +
                "    <item\n" +
                "        android:icon=\"@android:drawable/ic_menu_more\"\n" +
                "        android:title=\"@string/account_list_menu_more\">\n" +
                "        <menu>\n" +
                "            <item\n" +
                "                android:id=\"@+id/account_list_menu_backup_restore\"\n" +
                "                android:icon=\"@android:drawable/ic_menu_save\"\n" +
                "                android:title=\"@string/account_list_menu_backup_restore\"/>\n" +
                "        </menu>\n" +
                "    </item>\n" +
                "\n" +
                "</menu>");

    }

    @Test
    public void testColors1() throws Exception {
        checkFormat(
                XmlFormatPreferences.defaults(),
                "<resources>\n" +
                "  <color name=\"enrollment_error\">#99e21f14</color>\n" +
                "\n" +
                "  <color name=\"service_starting_up\">#99000000</color>\n" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "    <color name=\"enrollment_error\">#99e21f14</color>\n" +
                "    <color name=\"service_starting_up\">#99000000</color>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testEclipseFormatStyle1() throws Exception {
        XmlFormatPreferences prefs = new XmlFormatPreferences() {
            @Override
            public String getOneIndentUnit() {
                return "\t";
            }

            @Override
            public int getTabWidth() {
                return 8;
            }
        };
        checkFormat(
                prefs,
                "<resources>\n" +
                "  <color name=\"enrollment_error\">#99e21f14</color>\n" +
                "\n" +
                "  <color name=\"service_starting_up\">#99000000</color>\n" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "\t<color name=\"enrollment_error\">#99e21f14</color>\n" +
                "\t<color name=\"service_starting_up\">#99000000</color>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testEclipseFormatStyle2() throws Exception {
        XmlFormatPreferences prefs = new XmlFormatPreferences() {
            @Override
            public String getOneIndentUnit() {
                return "  ";
            }

            @Override
            public int getTabWidth() {
                return 2;
            }
        };
        prefs.useEclipseIndent = true;
        checkFormat(
                prefs,
                "<resources>\n" +
                "  <color name=\"enrollment_error\">#99e21f14</color>\n" +
                "\n" +
                "  <color name=\"service_starting_up\">#99000000</color>\n" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "  <color name=\"enrollment_error\">#99e21f14</color>\n" +
                "  <color name=\"service_starting_up\">#99000000</color>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testNameSorting() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "    <attr format=\"integer\" name=\"no\" />\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <attr name=\"no\" format=\"integer\" />\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testStableText() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
                "    android:layout_width=\"match_parent\"\n" +
                "    android:layout_height=\"match_parent\"\n" +
                "    android:orientation=\"vertical\">\n" +
                "    Hello World\n" +
                "\n" +
                "</LinearLayout>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
                "    android:layout_width=\"match_parent\"\n" +
                "    android:layout_height=\"match_parent\"\n" +
                "    android:orientation=\"vertical\" >\n" +
                "    Hello World\n" +
                "\n" +
                "</LinearLayout>");
    }

    @Test
    public void testResources1() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "        <string name=\"test_string\">a\n" +
                "                </string>\n" +
                "\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <string name=\"test_string\">a</string>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testMarkup() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "<string name=\"welcome\">Welcome to <b>Android</b>!</string>" +
                "<string name=\"glob_settings_top_text\"><b>To install a 24 Clock Widget, " +
                "please <i>long press</i> in Home Screen.</b> Configure the Global Settings " +
                "here.</string>" +
                "" +
                "\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <string name=\"welcome\">Welcome to <b>Android</b>!</string>\n" +
                "    <string name=\"glob_settings_top_text\"><b>To install a 24 Clock Widget, " +
                "please <i>long press</i> in Home Screen.</b> Configure the Global Settings " +
                "here.</string>\n" +
                "\n" +
                "</resources>");
    }

    /* This test fails when run on a plain DOM; when used with the Eclipse DOM for example
       where we can get access to the original DOM, as in EclipseXmlPrettyPrinter, it works
       public void testPreserveEntities() throws Exception {
        // Ensure that entities such as &gt; in the input string are preserved in the output
        // format
        checkFormat(
                "res/values/strings.xml",
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources><string name=\"untitled\">&lt;untitled2></string>\n" +
                "<string name=\"untitled2\">&lt;untitled2&gt;</string>\n" +
                "<string name=\"untitled3\">&apos;untitled3&quot;</string></resources>\n",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <string name=\"untitled\">&lt;untitled2></string>\n" +
                "    <string name=\"untitled2\">&lt;untitled2&gt;</string>\n" +
                "    <string name=\"untitled3\">&apos;untitled3&quot;</string>\n" +
                "\n" +
                "</resources>");
    }
    */

    @Test
    public void testCData1() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "    <string name=\"foo\"><![CDATA[bar]]></string>\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <string name=\"foo\"><![CDATA[bar]]></string>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testCData2() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "    <string name=\"foo1\"><![CDATA[bar1\n" +
                "bar2\n" +
                "bar3]]></string>\n" +
                "    <string name=\"foo2\"><![CDATA[bar]]></string>\n" +
                "</resources>",

                //"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "\n" +
                "    <string name=\"foo1\">\n" +
                "<![CDATA[bar1\n" +
                "bar2\n" +
                "bar3]]>\n" +
                "    </string>\n" +
                "    <string name=\"foo2\"><![CDATA[bar]]></string>\n" +
                "\n" +
                "</resources>");
    }

    @Test
    public void testComplexString() throws Exception {
        checkFormat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "<string name=\"progress_completed_export_all\">The database has " +
                "<b>successfully</b> been exported into: <br /><br /><font size=\"14\">" +
                "\\\"<i>%s</i>\\\"</font></string>" +
                "</resources>",

                "<resources>\n" +
                "\n" +
                "    <string name=\"progress_completed_export_all\">The database has " +
                "<b>successfully</b> been exported into: <br /><br /><font size=\"14\">" +
                "\\\"<i>%s</i>\\\"</font></string>\n" +
                "\n" +
                "</resources>");
    }


    @Test
    public void testToXml() throws Exception {
        Document doc = createEmptyPlainDocument();
        assertNotNull(doc);
        Element root = doc.createElement("myroot");
        doc.appendChild(root);
        root.setAttribute("foo", "bar");
        root.setAttribute("baz", "baz");
        Element child = doc.createElement("mychild");
        root.appendChild(child);
        Element child2 = doc.createElement("hasComment");
        root.appendChild(child2);
        Node comment = doc.createComment("This is my comment");
        child2.appendChild(comment);
        Element child3 = doc.createElement("hasText");
        root.appendChild(child3);
        Node text = doc.createTextNode("  This is my text  ");
        child3.appendChild(text);

        String xml = XmlUtils.toXml(doc);
        assertEquals(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                        "<myroot baz=\"baz\" foo=\"bar\"><mychild/><hasComment><!--This is my comment--></hasComment><hasText>  This is my text  </hasText></myroot>",
                xml);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<myroot\n"
                + "    baz=\"baz\"\n"
                + "    foo=\"bar\" >\n"
                + "\n"
                + "    <mychild />\n"
                + "\n"
                + "    <hasComment> <!-- This is my comment -->\n"
                + "    </hasComment>\n"
                + "\n"
                + "    <hasText>\n"
                + "  This is my text  \n"
                + "    </hasText>\n"
                + "\n"
                + "</myroot>",
                xml);
    }

    @Test
    public void testToXml2() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "    <string \n"
                + "        name=\"description_search\">Search</string>\n"
                + "    <string \n"
                + "        name=\"description_map\">Map</string>\n"
                + "    <string\n"
                + "         name=\"description_refresh\">Refresh</string>\n"
                + "    <string \n"
                + "        name=\"description_share\">Share</string>\n"
                + "</resources>";

        Document doc = parse(xml);
        assertNotNull(doc);

        String formatted = XmlUtils.toXml(doc);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "    <string name=\"description_search\">Search</string>\n"
                + "    <string name=\"description_map\">Map</string>\n"
                + "    <string name=\"description_refresh\">Refresh</string>\n"
                + "    <string name=\"description_share\">Share</string>\n"
                + "</resources>",
                formatted);

        formatted = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "\n"
                + "    <string name=\"description_search\">Search</string>\n"
                + "    <string name=\"description_map\">Map</string>\n"
                + "    <string name=\"description_refresh\">Refresh</string>\n"
                + "    <string name=\"description_share\">Share</string>\n"
                + "\n"
                + "</resources>",
                formatted);
    }

    @Test
    public void testToXml3() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<root>\n"
                + "    <!-- ============== -->\n"
                + "    <!-- Generic styles -->\n"
                + "    <!-- ============== -->\n"
                + "</root>";
        Document doc = parse(xml);
        assertNotNull(doc);

        String formatted = XmlUtils.toXml(doc);
        assertEquals(xml, formatted);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<root>\n"
                + "\n"
                + "    <!-- ============== -->\n"
                + "    <!-- Generic styles -->\n"
                + "    <!-- ============== -->\n"
                + "\n"
                + "</root>",
                xml);
    }

    @Test
    public void testToXml3b() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "  <!-- ============== -->\n"
                + "  <!-- Generic styles -->\n"
                + "         <!-- ============== -->\n"
                + " <string     name=\"test\">test</string>\n"
                + "</resources>";
        Document doc = parse(xml);
        assertNotNull(doc);

        String formatted = XmlUtils.toXml(doc);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "  <!-- ============== -->\n"
                + "  <!-- Generic styles -->\n"
                + "         <!-- ============== -->\n"
                + " <string name=\"test\">test</string>\n"
                + "</resources>",
                formatted);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "\n"
                + "    <!-- ============== -->\n"
                + "    <!-- Generic styles -->\n"
                + "    <!-- ============== -->\n"
                + "    <string name=\"test\">test</string>\n"
                + "\n"
                + "</resources>",
                xml);
    }


    public void testToXml4() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<!-- ============== -->\n"
                + "<!-- Generic styles -->\n"
                + "<!-- ============== -->\n"
                + "<root/>";
        Document doc = parse(xml);
        assertNotNull(doc);

        xml = XmlUtils.toXml(doc);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<!-- ============== --><!-- Generic styles --><!-- ============== --><root/>",
                xml);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<!-- ============== -->\n"
                + "<!-- Generic styles -->\n"
                + "<!-- ============== -->\n"
                + "<root />\n",
                xml);
    }

    public void testToXml5() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources><attr name=\"flag_attr\">"
                + "<flag name=\"normal\" value=\"0\"/>"
                + "<flag name=\"bold\" value=\"1\"/>"
                + "<flag name=\"italic\" "
                + "value=\"2\"/></attr></resources>";

        Document doc = parse(xml);
        assertNotNull(doc);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources>\n"
                + "\n"
                + "    <attr name=\"flag_attr\">\n"
                + "        <flag name=\"normal\" value=\"0\" />\n"
                + "        <flag name=\"bold\" value=\"1\" />\n"
                + "        <flag name=\"italic\" value=\"2\" />\n"
                + "    </attr>\n"
                + "\n"
                + "</resources>",
                xml);
    }

    public void testMarkupSpacing() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
                + "\n"
                + "    <string name=\"basic_string\">basic_string</string>\n"
                + "    <string name=\"markup\">this is a <b>bold</b>  <b>string</b> </string>\n"
                + "    <string name=\"xliff_string\"><xliff:g id=\"firstName\">%1$s</xliff:g> <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
                + "    <string name=\"styled_string\">Forgot your username or password\\?\\nVisit <b>google.com/accounts/recovery</b>.</string>\n"
                + "\n"
                + "    <plurals name=\"plurals\">\n"
                + "        <item quantity=\"one\">test2 <xliff:g xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" id=\"test3\">%s</xliff:g> test4</item>\n"
                + "    </plurals>\n"
                + "</resources>\n";

        Document doc = parse(xml);
        assertNotNull(doc);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
                + "\n"
                + "    <string name=\"basic_string\">basic_string</string>\n"
                + "    <string name=\"markup\">this is a <b>bold</b> <b>string</b></string>\n"
                + "    <string name=\"xliff_string\"><xliff:g id=\"firstName\">%1$s</xliff:g> <xliff:g id=\"lastName\">%2$s</xliff:g></string>\n"
                + "    <string name=\"styled_string\">Forgot your username or password\\?\\nVisit <b>google.com/accounts/recovery</b>.</string>\n"
                + "\n"
                + "    <plurals name=\"plurals\">\n"
                + "        <item quantity=\"one\">test2 <xliff:g id=\"test3\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">%s</xliff:g> test4</item>\n"
                + "    </plurals>\n"
                + "\n"
                + "</resources>",

                xml);
    }

    public void testXliff() throws Exception {
        String xml = ""
                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
                + "    <string name=\"xliff\">Text:\n"
                + "        <xliff:g id=\"firstName\">%1$s</xliff:g></string>\n"
                + "    <string name=\"xliff2\">Name:<xliff:g id=\"firstName\"> %1$s</xliff:g></string>\n"
                + "</resources>";

        Document doc = parse(xml);
        assertNotNull(doc);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
                + "\n"
                + "    <string name=\"xliff\">Text: <xliff:g id=\"firstName\">%1$s</xliff:g></string>\n"
                + "    <string name=\"xliff2\">Name:<xliff:g id=\"firstName\"> %1$s</xliff:g></string>\n"
                + "\n"
                + "</resources>",
            xml);
    }

    public void test52887() throws Exception {
        // https://code.google.com/p/android/issues/detail?id=52887
        String xml = ""
                + "<!--Comment-->\n"
                + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                + "\t\t    android:layout_width=\"match_parent\"\n"
                + "    android:layout_height=\"match_parent\"/>\n";

        Document doc = parse(xml);
        assertNotNull(doc);

        xml = XmlPrettyPrinter.prettyPrint(doc, false);
        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<!-- Comment -->\n"
                + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                + "    android:layout_width=\"match_parent\"\n"
                + "    android:layout_height=\"match_parent\" />\n",
                xml);
    }

    public void testPreserveNewlines() throws Exception {
        XmlFormatPreferences prefs = XmlFormatPreferences.defaults();
        XmlFormatStyle style = XmlFormatStyle.LAYOUT;

        String before = "<LinearLayout><Button></Button></LinearLayout>\n";

        String expected =
                "<LinearLayout>\n" +
                "\n" +
                "    <Button />\n" +
                "\n" +
                "</LinearLayout>\n";

        String after = XmlPrettyPrinter.prettyPrint(before, prefs, style, "\n");
        assertEquals(expected, after);
    }

    public void testDriver1() throws Exception {
        checkDriver(""
                + "Usage: XmlPrettyPrinter <options>... <files or directories...>\n"
                + "OPTIONS:\n"
                + "--stdout\n"
                + "--removeEmptyLines\n"
                + "--noAttributeOnFirstLine\n"
                + "--noSpaceBeforeClose\n",
                "Unknown flag --nonexistentFlag\n",
                1,
                new String[]{"--nonexistentFlag"},
                null
        );
    }

    public void testDriver2() throws Exception {
        String brokenXml = "<view>\n"
                + "<notclosed>\n"
                + "</view>";
        File temp = File.createTempFile("mylayout", ".xml");
        Files.write(brokenXml, temp, Charsets.UTF_8);
        checkDriver(
                "",
                "[Fatal Error] :3:3: The element type \"notclosed\" must be terminated by "
                        + "the matching end-tag \"</notclosed>\".\n"
                        + "Could not parse $TESTFILE\n",
                1,
                new String[]{"--stdout", temp.getPath()},
                temp
        );
        //noinspection ResultOfMethodCallIgnored
        temp.delete();
    }

    public void testDriver3() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\""
                + " xmlns:tools=\"http://schemas.android.com/tools\""
                + " android:layout_width=\"match_parent\" android:layout_height=\"match_parent\""
                + " android:orientation=\"vertical\"  tools:ignore=\"HardcodedText\">"
                + "\n"
                + "        <!-- Comment -->\n"
                + "</LinearLayout>";
        File temp = File.createTempFile("mylayout", ".xml");
        Files.write(xml, temp, Charsets.UTF_8);
        checkDriver(
                "",
                "",
                0,
                new String[]{temp.getPath()},
                temp
        );

        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                + "    xmlns:tools=\"http://schemas.android.com/tools\"\n"
                + "    android:layout_width=\"match_parent\"\n"
                + "    android:layout_height=\"match_parent\"\n"
                + "    android:orientation=\"vertical\"\n"
                + "    tools:ignore=\"HardcodedText\" >\n"
                + "\n"
                + "    <!-- Comment -->\n"
                + "\n"
                + "</LinearLayout>",
                Files.toString(temp, Charsets.UTF_8));
        //noinspection ResultOfMethodCallIgnored
        temp.delete();
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public void testDriver4() throws Exception {
        File root = TestUtils.createTempDirDeletedOnExit();
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\""
                + " xmlns:tools=\"http://schemas.android.com/tools\""
                + " android:layout_width=\"match_parent\" android:layout_height=\"match_parent\""
                + " android:orientation=\"vertical\"  tools:ignore=\"HardcodedText\">"
                + "\n"
                + "        <!-- Comment -->\n"
                + "</LinearLayout>";
        File file1 = new File(root, "layout1.xml");
        Files.write(xml, file1, Charsets.UTF_8);
        File dir1 = new File(root, "layout");
        dir1.mkdirs();
        File file2 = new File(dir1, "layout2.xml");
        Files.write(xml, file2, Charsets.UTF_8);

        checkDriver(
                "",
                "",
                0,
                new String[]{file1.getPath(), dir1.getPath()},
                root
        );

        String formatted = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
                + "    xmlns:tools=\"http://schemas.android.com/tools\"\n"
                + "    android:layout_width=\"match_parent\"\n"
                + "    android:layout_height=\"match_parent\"\n"
                + "    android:orientation=\"vertical\"\n"
                + "    tools:ignore=\"HardcodedText\" >\n"
                + "\n"
                + "    <!-- Comment -->\n"
                + "\n"
                + "</LinearLayout>";

        assertEquals(formatted, Files.toString(file1, Charsets.UTF_8));
        assertEquals(formatted, Files.toString(file2, Charsets.UTF_8));

        file1.delete();
        file2.delete();
        dir1.delete();
        root.delete();
    }

    public void testDriver5() throws Exception {
        String xml = ""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
                + "<Button></Button>"
                + "<Button/>"
                + "</LinearLayout>";
        File temp = File.createTempFile("mylayout", ".xml");
        Files.write(xml, temp, Charsets.UTF_8);
        checkDriver(
                "",
                "",
                0,
                new String[]{temp.getPath()},
                temp
        );

        // Note limitation of command line XML parser: Can't distinguish
        // between <element></element> and <element/>

        assertEquals(""
                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n"
                + "\n"
                + "    <Button />\n"
                + "\n"
                + "    <Button />\n"
                + "\n"
                + "</LinearLayout>",
                Files.toString(temp, Charsets.UTF_8));
        //noinspection ResultOfMethodCallIgnored
        temp.delete();
    }

    @Nullable
    private static Document createEmptyPlainDocument() throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setValidating(false);
        factory.setIgnoringComments(true);
        DocumentBuilder builder;
        builder = factory.newDocumentBuilder();
        return builder.newDocument();
    }

    @SuppressWarnings("null")
    @NonNull
    private static Document parse(String xml) throws Exception {
        Document doc = XmlUtils.parseDocumentSilently(xml, true);
        assertNotNull(doc);
        return doc;
    }

    private static void checkDriver(String expectedOutput, String expectedError,
            int expectedExitCode, String[] args, File testFile)
            throws Exception {
        PrintStream previousOut = System.out;
        PrintStream previousErr = System.err;
        try {
            // Trap System.exit calls:
            System.setSecurityManager(new SecurityManager() {
                @Override
                public void checkPermission(Permission perm)
                {
                    // allow anything.
                }
                @Override
                public void checkPermission(Permission perm, Object context)
                {
                    // allow anything.
                }
                @Override
                public void checkExit(int status) {
                    throw new ExitException(status);
                }
            });

            final ByteArrayOutputStream output = new ByteArrayOutputStream();
            System.setOut(new PrintStream(output));
            final ByteArrayOutputStream error = new ByteArrayOutputStream();
            System.setErr(new PrintStream(error));

            int exitCode = 0xCAFEBABE; // not set
            try {
                XmlPrettyPrinter.main(args);
            } catch (ExitException e) {
                // Allow
                exitCode = e.getStatus();
            }

            String testPath = testFile == null
                    ? XmlPrettyPrinterTest.class.getName() : testFile.getPath();
            String pathName = "$TESTFILE";
            assertEquals(expectedError, error.toString().replace(testPath, pathName));
            assertEquals(expectedOutput, output.toString().replace(testPath, pathName));
            assertEquals(expectedExitCode, exitCode);
        } finally {
            // Re-enable system exit for unit test
            System.setSecurityManager(null);

            System.setOut(previousOut);
            System.setErr(previousErr);
        }
    }

    private static class ExitException extends SecurityException {
        private static final long serialVersionUID = 1L;

        private final int mStatus;

        public ExitException(int status) {
            super("Unit test");
            mStatus = status;
        }

        public int getStatus() {
            return mStatus;
        }
    }
}
