package com.bagri.core.server.api.df.xml;
import static com.bagri.core.Constants.pn_schema_builder_pretty;
import static com.bagri.core.Constants.pn_schema_builder_ident;
import static com.bagri.support.util.FileUtils.EOL;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Properties;
import javax.xml.stream.XMLOutputFactory;
import com.bagri.core.api.BagriException;
import com.bagri.core.model.Data;
import com.bagri.core.model.NodeKind;
import com.bagri.core.server.api.ContentBuilder;
import com.bagri.core.server.api.ModelManagement;
import com.bagri.core.server.api.impl.ContentBuilderBase;
/**
* XDM Builder implementation for XML format.
*
* @author Denis Sukhoroslov
*
*/
public class XmlBuilder extends ContentBuilderBase<String> implements ContentBuilder<String> {
// TODO: try this..
//private XMLOutputFactory factory = XMLOutputFactory.newInstance();
private boolean pretty = false;
private int ident = 4;
/**
*
* @param model the XDM model management component
*/
XmlBuilder(ModelManagement model) {
super(model);
}
/**
* {@inheritDoc}
*/
public void init(Properties properties) {
logger.trace("init; got properties: {}", properties);
String value = properties.getProperty(pn_schema_builder_pretty, "false");
pretty = Boolean.valueOf(value);
value = properties.getProperty(pn_schema_builder_ident, "4");
ident = Integer.parseInt(value);
}
/**
* {@inheritDoc}
*/
@Override
public String buildContent(Collection<Data> elements) throws BagriException {
Deque<Data> dataStack = new LinkedList<>();
StringBuffer buff = new StringBuffer();
boolean eltOpen = false;
String prefix = "";
for (int i=0; i < ident; i++) {
prefix += " ";
}
for (Data data: elements) {
eltOpen = writeElement(dataStack, buff, data, eltOpen, prefix);
}
boolean writeIdent = false;
while (dataStack.size() > 0) {
Data top = dataStack.pop();
if (eltOpen) {
buff.append("/>");
eltOpen = false;
} else {
if (writeIdent) {
addIdents(top, buff, prefix);
}
buff.append("</").append(top.getDataPath().getXmlName()).append(">");
}
if (pretty) {
buff.append(EOL);
writeIdent = true;
}
}
return buff.toString();
}
private boolean writeElement(Deque<Data> dataStack, StringBuffer buff, Data data, boolean eltOpen, String prefix) {
switch (data.getNodeKind()) {
case document: { // this must be the first row..
buff.append("<?xml version=\"1.0\"?>"); // what about: encoding="utf-8"?>, standalone..
if (pretty) {
buff.append(EOL);
}
break;
}
case namespace: {
buff.append(" ").append("xmlns");
String name = data.getDataPath().getXmlName();
if (name != null && name.trim().length() > 0) {
buff.append(":").append(name);
}
buff.append("=\"").append(data.getValue()).append("\"");
break;
}
case element: {
eltOpen = endElement(dataStack, buff, data, eltOpen, prefix);
dataStack.push(data);
// don't add idents if we're in mixed content (text)!
addIdents(data, buff, prefix);
buff.append("<").append(data.getDataPath().getXmlName());
eltOpen = true;
break;
}
case attribute: {
if (!dataStack.isEmpty()) {
buff.append(" ").append(data.getDataPath().getXmlName()).append("=\"").append(data.getValue()).append("\"");
} else {
buff.append(data.getValue());
}
break;
}
case comment: {
eltOpen = endElement(dataStack, buff, data, eltOpen, prefix);
addIdents(data, buff, prefix);
buff.append("<!--").append(data.getValue()).append("-->");
if (dataStack.isEmpty() && pretty) {
buff.append(EOL);
}
break;
}
case pi: {
eltOpen = endElement(dataStack, buff, data, eltOpen, prefix);
addIdents(data, buff, prefix);
buff.append("<?").append(data.getDataPath().getXmlName()).append(" ");
buff.append(data.getValue()).append("?>");
if (dataStack.isEmpty() && pretty) {
buff.append(EOL);
}
break;
}
case text: {
eltOpen = endElement(dataStack, buff, data, eltOpen, prefix);
buff.append(data.getValue());
break;
}
default: {
//logger.warn("buildXml; unknown NodeKind: {}", data.getNodeKind());
}
}
return eltOpen;
}
private boolean endElement(Deque<Data> dataStack, StringBuffer buff, Data data, boolean eltOpen, String prefix) {
if (dataStack.isEmpty()) {
//
} else {
Data top = dataStack.peek();
if (data.getLevel() == top.getLevel() + 1 && data.getParentPos() == top.getPos()) {
// new child element
if (eltOpen) {
buff.append(">");
eltOpen = false;
}
if (data.getNodeKind() != NodeKind.text && pretty) {
// don't add EOL if we're in mixed content (text)!
buff.append(EOL);
}
} else {
boolean writeIdent = false;
while (top != null && (data.getParentPos() != top.getPos() || data.getLevel() != top.getLevel() + 1)) {
if (eltOpen) {
buff.append("/>");
eltOpen = false;
} else {
if (writeIdent) {
addIdents(top, buff, prefix);
}
buff.append("</").append(top.getDataPath().getXmlName()).append(">");
}
if (pretty) {
buff.append(EOL);
writeIdent = true;
}
dataStack.pop();
top = dataStack.peek();
}
}
}
return eltOpen;
}
private void addIdents(Data data, StringBuffer buff, String space) {
if (pretty) {
for (int i=1; i < data.getLevel(); i++) {
buff.append(space);
}
}
}
}