/* ScrutariDataExport - Java implementation of ScrutariDataExport API
 * http://www.scrutari.net/dokuwiki/scrutaridata:exportapi
 *
 * Copyright (c) 2010-2025 Vincent Calame - Exemole
 * Licensed under MIT
 * (http://en.wikipedia.org/wiki/MIT_License)
 */


package net.scrutari.dataexport.xml;

import java.io.IOException;
import net.scrutari.dataexport.api.ExportIOException;


/**
 *
 * @author Vincent Calame
 */
public class XmlWriter {

    private final Appendable appendable;
    private final boolean signToBracketEscape;
    private int indentLength = 0;

    public XmlWriter(Appendable appendable, int indentLength, boolean includeXMLDeclaration, boolean signToBracketEscape) {
        this.appendable = appendable;
        this.indentLength = indentLength;
        this.signToBracketEscape = signToBracketEscape;
        if (includeXMLDeclaration) {
            appendXMLDeclaration();
        }
    }

    private void appendXMLDeclaration() {
        append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    }

    public void addEmptyElement(String name) {
        appendIndent();
        append('<');
        append(name);
        append("/>");
    }

    public void addEmptyElement(String name, String[] attributeNameArray, String[] attributeValueArray) {
        appendIndent();
        append('<');
        append(name);
        if (attributeNameArray != null) {
            for (int i = 0; i < attributeNameArray.length; i++) {
                addAttribute(attributeNameArray[i], attributeValueArray[i]);
            }
        }
        append("/>");
    }

    public void openTag(String name) {
        appendIndent();
        append('<');
        append(name);
        append('>');
        increaseIndentValue();
    }

    public void openTagWithAttribute(String name, String attributeName, String attributeValue) {
        startOpenTag(name);
        addAttribute(attributeName, attributeValue);
        endOpenTag(true);
    }

    public void openTagWithAttributes(String name, String[] attributeNameArray, String[] attributeValueArray) {
        startOpenTag(name);
        for (int i = 0; i < attributeNameArray.length; i++) {
            addAttribute(attributeNameArray[i], attributeValueArray[i]);
        }
        endOpenTag(true);
    }

    public void closeTag(String name) {
        closeTag(name, true);
    }

    public void append(char c) {
        try {
            appendable.append(c);
        } catch (IOException ioe) {
            throw new ExportIOException(ioe);
        }
    }

    public void append(CharSequence charSequence) {
        try {
            appendable.append(charSequence);
        } catch (IOException ioe) {
            throw new ExportIOException(ioe);
        }
    }

    public void escape(CharSequence charSequence) {
        if (charSequence == null) {
            return;
        }
        int length = charSequence.length();
        for (int i = 0; i < length; i++) {
            char carac = charSequence.charAt(i);
            escape(carac);
        }
    }

    public void addSimpleElement(String tagname, String value) {
        if ((value == null) || (value.length() == 0)) {
            return;
        }
        startOpenTag(tagname);
        endOpenTag(false);
        escape(value);
        closeTag(tagname, false);
    }

    public void addLibElement(String lang, String lib) {
        if ((lang == null) || (lang.length() == 0)) {
            lang = "und";
        }
        if (lang.equals("zxx")) {
            return;
        }
        startOpenTag("lib");
        addAttribute("xml:lang", lang);
        endOpenTag(false);
        escape(lib);
        closeTag("lib", false);
    }

    public void addLabelElement(String lang, String text) {
        if ((lang == null) || (lang.length() == 0)) {
            lang = "und";
        }
        if (lang.equals("zxx")) {
            return;
        }
        startOpenTag("label");
        addAttribute("xml:lang", lang);
        endOpenTag(false);
        escape(text);
        closeTag("label", false);
    }

    public void escape(char carac) {
        if (carac < 32) {
            append(' ');
        } else {
            switch (carac) {
                case '&':
                    append("&amp;");
                    break;
                case '"':
                    append("&quot;");
                    break;
                case '\'':
                    append("&apos;");
                    break;
                case '<':
                    if (signToBracketEscape) {
                        append('〈');
                    } else {
                        append("&lt;");
                    }
                    break;
                case '>':
                    if (signToBracketEscape) {
                        append('〉');
                    } else {
                        append("&gt;");
                    }
                    break;
                case '\u00A0':
                    append("&#x00A0;");
                    break;
                default:
                    append(carac);
            }
        }
    }

    private void startOpenTag(String name) {
        appendIndent();
        append('<');
        append(name);
    }

    private void endOpenTag(boolean increaseIndent) {
        append('>');
        if (increaseIndent) {
            increaseIndentValue();
        }
    }

    private void addAttribute(String attributeName, String value) {
        if ((value == null) || (value.length() == 0)) {
            return;
        }
        append(' ');
        append(attributeName);
        append('=');
        append('\"');
        escape(value);
        append('\"');
    }

    private void closeTag(String name, boolean newLine) {
        if (newLine) {
            decreaseIndentValue();
            appendIndent();
        }
        append('<');
        append('/');
        append(name);
        append('>');
    }

    private void appendIndent() {
        if (indentLength > - 1) {
            append('\n');
            for (int i = 0; i < indentLength; i++) {
                append('\t');
            }
        }
    }

    private void increaseIndentValue() {
        indentLength++;
    }

    private void decreaseIndentValue() {
        indentLength--;
    }

}
