package folioxml.export.plugins;
import folioxml.config.*;
import folioxml.core.InvalidMarkupException;
import folioxml.export.FileNode;
import folioxml.export.InfobaseSetPlugin;
import folioxml.export.LogStreamProvider;
import folioxml.slx.ISlxTokenReader;
import folioxml.slx.SlxRecord;
import folioxml.xml.*;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayDeque;
import java.util.Deque;
public class ExportXmlFile implements InfobaseSetPlugin {
public ExportXmlFile() {
}
public ExportXmlFile(Boolean indentXml) {
this.indentXml = indentXml;
}
private Boolean indentXml = null;
private Deque<FileNode> openFileNodes = null;
protected Writer out;
private int indentLevel = 0;
private String indentString = " ";
private Boolean skipNormalRecords = true;
private Boolean nestFileElements = true;
@Override
public void beginInfobaseSet(InfobaseSet set, ExportLocations export, LogStreamProvider logs) throws IOException {
out = Files.newBufferedWriter(export.getLocalPath(set.getId() + ".xml", AssetType.Xml, FolderCreation.CreateParents), Charset.forName("UTF-8"));
out.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
openElement("infobases");
skipNormalRecords = set.getBool("skip_normal_records");
if (skipNormalRecords == null) skipNormalRecords = false;
nestFileElements = set.getBool("nest_file_elements");
if (nestFileElements == null) nestFileElements = true;
if (indentXml == null)
indentXml = set.getBool("indent_xml");
if (indentXml == null) indentXml = false;
}
boolean infobaseTagOpened = false;
InfobaseConfig current = null;
@Override
public void beginInfobase(InfobaseConfig infobase) throws IOException {
current = infobase;
openFileNodes = new ArrayDeque<FileNode>();
}
@Override
public ISlxTokenReader wrapSlxReader(ISlxTokenReader reader) {
return reader;
}
@Override
public void onSlxRecordParsed(SlxRecord clean_slx) throws InvalidMarkupException, IOException {
}
@Override
public void onRecordTransformed(XmlRecord xr, SlxRecord dirty_slx) throws InvalidMarkupException, IOException {
}
@Override
public FileNode assignFileNode(XmlRecord xr, SlxRecord dirty_slx) throws InvalidMarkupException, IOException {
return null;
}
@Override
public void onRecordComplete(XmlRecord xr, FileNode file) throws InvalidMarkupException, IOException {
if (xr.isRootRecord() == infobaseTagOpened)
throw new InvalidMarkupException("The first record of every infobase should be the root record");
if (!infobaseTagOpened) {
String author = new NodeList(xr).search(new NodeFilter("infobase-meta", "type", "author")).getTextContents().trim();
NodeList titleElements = new NodeList(xr).search(new NodeFilter("infobase-meta", "type", "title"));
String title = titleElements.count() > 0 ? titleElements.first().get("content").trim() : "";
Node n = new Node("<infobase></infobase>");
n.set("author", author);
n.set("title", title);
n.set("name", current.getId());
n.set("levelDefOrder", xr.get("levelDefOrder"));
n.set("levels", xr.get("levels"));
openElement(n);
infobaseTagOpened = true;
} else {
boolean skip = skipNormalRecords && !xr.isLevelRecord();
if (nestFileElements) {
FileNode common = openFileNodes.isEmpty() ? null : getCommonAncestor(file, openFileNodes.peek(), true);
closeAllUntil(common);
if (common != file) {
openChildren();
openFile(file); //We can't descend more than one node at a time; every file node must have 1 or more records.
}
if (skip) return;
openBody();
} else {
closeAllUntil(file);
if (skip) return;
openFile(file);
}
if (indentXml)
out.write(new XmlFormatter(indentLevel, indentString).format(xr));
else
out.write(xr.toXmlString(false));
}
}
public Deque<FileNode> getAncestors(FileNode n, boolean includeSelf) {
Deque<FileNode> parents = new ArrayDeque<FileNode>();
FileNode current = n;
if (includeSelf) parents.add(n);
while (current.getParent() != null) {
parents.addLast(current.getParent());
current = current.getParent();
}
return parents;
}
public FileNode getCommonAncestor(FileNode a, FileNode b, boolean includeSelves) {
Deque<FileNode> parents = getAncestors(a, includeSelves);
Deque<FileNode> otherParents = getAncestors(b, includeSelves);
FileNode common = null;
while (!parents.isEmpty() && !otherParents.isEmpty()) {
FileNode c = parents.removeLast();
if (c == otherParents.removeLast()) common = c;
else break;
}
return common;
}
private boolean getBool(FileNode n, String key, boolean defaultValue) {
Object o = n.getBag().get(key);
return o == null ? defaultValue : (Boolean) o;
}
private void setBool(FileNode n, String key, boolean value) {
n.getBag().put(key, value);
}
private void openFile(FileNode n) throws IOException, InvalidMarkupException {
if (!openFileNodes.isEmpty() && openFileNodes.peek() == n) return; //Don't open the same node twice
openFileNodes.push(n);
Node xn = new Node("<file></file>");
xn.getAttributes().putAll(n.getAttributes());
openElement(xn);
}
private void openBody() throws IOException {
FileNode top = openFileNodes.peek();
if (!getBool(top, "bodyOpen", false)) {
openElement("body");
setBool(top, "bodyOpen", true);
}
}
private void openChildren() throws IOException {
FileNode top = openFileNodes.peek();
if (top == null) return;
if (getBool(top, "bodyOpen", false)) {
closeElement("body");
setBool(top, "bodyOpen", false);
}
if (!getBool(top, "childrenOpen", false)) {
openElement("children");
setBool(top, "childrenOpen", true);
}
//Close body tag if open, open children tag.
}
private void closeAllUntil(FileNode until) throws IOException {
while (!openFileNodes.isEmpty()) {
if (openFileNodes.peek() == until) return;
FileNode top = openFileNodes.removeFirst();
if (getBool(top, "bodyOpen", false)) {
closeElement("body");
setBool(top, "bodyOpen", false);
}
if (getBool(top, "childrenOpen", false)) {
closeElement("children");
setBool(top, "childrenOpen", false);
}
closeElement("file");
}
}
private void writeIndent() throws IOException {
for (int i = 0; i < indentLevel; i++) {
out.append(indentString);
}
}
private void openElement(String elementName) throws IOException {
writeIndent();
out.append("<");
out.append(elementName);
out.append(">\n");
indentLevel++;
}
private void openElement(Node element) throws IOException, InvalidMarkupException {
StringBuilder sb = new StringBuilder();
writeIndent();
if (element.getAttributes().values().contains(null)) {
// throw new IOException("Null attribute value in " + element.toXmlString(true));
}
element.writeTokenTo(sb);
out.append(sb);
out.append("\n");
indentLevel++;
}
private void closeElement(String elementName) throws IOException {
indentLevel--;
writeIndent();
out.append("</");
out.append(elementName);
out.append(">\n");
}
@Override
public void endInfobase(InfobaseConfig infobase) throws IOException {
if (infobaseTagOpened) {
closeAllUntil(null);
closeElement("infobase");
infobaseTagOpened = false;
}
}
@Override
public void endInfobaseSet(InfobaseSet set) throws IOException {
closeElement("infobases");
out.close();
}
}