XmlUtilTest.java

/**
 * Copyright (c) 2019, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.commons.xml;

import com.google.common.collect.ImmutableMap;
import com.powsybl.commons.PowsyblException;
import org.junit.jupiter.api.Test;

import javax.xml.stream.*;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class XmlUtilTest {

    private static final String XML = String.join(System.lineSeparator(),
            "<a>",
            "    <b attrBool=\"true\" attrInt=\"34\" attrDbl=\"2e-65\" attrFlt=\"0.054864\">",
            "        <c/>",
            "    </b>",
            "    <d/>",
            "</a>");

    @Test
    void readAttributes() throws XMLStreamException {
        AtomicReference<Boolean> attrBoolBoxed = new AtomicReference<>(false);
        AtomicBoolean attrBool = new AtomicBoolean(false);
        AtomicReference<Integer> attrInteger = new AtomicReference<>(-1);
        AtomicInteger attrInt = new AtomicInteger(-1);
        AtomicReference<Double> attrDbl = new AtomicReference<>(0d);
        AtomicReference<Float> attrFloat = new AtomicReference<>(0f);
        try (StringReader reader = new StringReader(XML)) {
            XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
            xmlReader.next();
            try {
                XmlUtil.readSubElements(xmlReader, elementName -> {
                    if ("b".equals(elementName)) {
                        attrBoolBoxed.set(XmlUtil.readBooleanAttribute(xmlReader, "attrBool"));
                        attrBool.set(XmlUtil.readBooleanAttribute(xmlReader, "attrBool", false));
                        attrInteger.set(XmlUtil.readIntegerAttribute(xmlReader, "attrInt"));
                        attrInt.set(XmlUtil.readIntAttribute(xmlReader, "attrInt", -1));
                        attrDbl.set(XmlUtil.readDoubleAttribute(xmlReader, "attrDbl", 0));
                        attrFloat.set(XmlUtil.readFloatAttribute(xmlReader, "attrFlt", 0));
                    }
                });
            } finally {
                xmlReader.close();
            }
        }

        assertTrue(attrBoolBoxed::get);
        assertTrue(attrBool.get());
        assertEquals(34, attrInteger.get());
        assertEquals(34, attrInt.get());
        assertEquals(2e-65, attrDbl.get(), 1e-80);
        assertEquals(0.054864f, attrFloat.get(), 1e-15);
    }

    @Test
    void readUntilEndElementWithDepthTest() throws XMLStreamException {
        Map<String, Integer> depths = new HashMap<>();
        try (StringReader reader = new StringReader(XML)) {
            XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
            xmlReader.next();
            try {
                XmlUtil.readSubElements(xmlReader, elementName -> {
                    depths.put(elementName, 0);
                    XmlUtil.readSubElements(xmlReader, elementName1 -> {
                        depths.put(elementName1, 1);
                        XmlUtil.skipSubElements(xmlReader);
                    });
                });
            } finally {
                xmlReader.close();
            }
        }
        assertEquals(ImmutableMap.of("b", 0, "c", 1, "d", 0), depths);
    }

    @Test
    void nestedReadUntilEndElementWithDepthTest() throws XMLStreamException {
        Map<String, Integer> depths = new HashMap<>();
        try (StringReader reader = new StringReader(XML)) {
            XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
            try {
                xmlReader.next();
                XmlUtil.readSubElements(xmlReader, elementName -> {
                    depths.put(elementName, 0);
                    // consume b and c
                    if (elementName.equals("b")) {
                        XmlUtil.readSubElements(xmlReader, elementName1 -> {
                            depths.put(elementName1, 1);
                            XmlUtil.skipSubElements(xmlReader);
                        });
                    }
                });
            } finally {
                xmlReader.close();
            }
        }
        assertEquals(ImmutableMap.of("b", 0, "c", 1, "d", 0), depths);
    }

    @Test
    void readUntilStartElementTest() throws XMLStreamException {
        readUntilStartElementTest("/a", "a");
        readUntilStartElementTest("/a/b/c", "c");
        readUntilStartElementTest("/a/d", "d");

        readUntilStartElementNotFoundTest("/a/e", "a");
        readUntilStartElementNotFoundTest("/a/b/a", "b");

        try {
            readUntilStartElementTest("/b", null);
        } catch (PowsyblException e) {
            assertEquals("Unable to find b: end of document has been reached", e.getMessage());
        }
    }

    private void readUntilStartElementTest(String path, String expected) throws XMLStreamException {
        try (StringReader reader = new StringReader(XML)) {
            XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
            try {
                XmlUtil.readUntilStartElement(path, xmlReader, elementName -> assertEquals(expected, xmlReader.getLocalName()));
            } finally {
                xmlReader.close();
            }
        }
    }

    private void readUntilStartElementNotFoundTest(String path, String parent) throws XMLStreamException {
        try {
            readUntilStartElementTest(path, null);
        } catch (PowsyblException e) {
            assertEquals("Unable to find " + path + ": parent element " + parent + " has been closed", e.getMessage());
        }
    }

    @Test
    void readTextTest() throws XMLStreamException {
        String xml = "<a>hello</a>";
        try (StringReader reader = new StringReader(xml)) {
            XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
            try {
                String text = null;
                while (xmlReader.hasNext()) {
                    int next = xmlReader.next();
                    if (next == XMLStreamConstants.START_ELEMENT && xmlReader.getLocalName().equals("a")) {
                        text = XmlUtil.readText(xmlReader);
                    }
                }
                assertEquals("hello", text);
            } finally {
                xmlReader.close();
            }
        }
    }

    @Test
    void initializeWriterDefault() throws XMLStreamException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        XMLStreamWriter writer = XmlUtil.initializeWriter(true, " ", baos);
        writer.close();
        assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", baos.toString());
    }

    @Test
    void initializeWriter() throws XMLStreamException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        XMLStreamWriter writer = XmlUtil.initializeWriter(false, " ", baos, StandardCharsets.ISO_8859_1);
        writer.close();
        assertEquals("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>", baos.toString());
    }
}