/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.xml;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Stack;
import divconq.io.CharSequenceReader;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationResult;
import divconq.util.StringUtil;
public class XmlReader implements IParseHandler {
/*
* Read and parse xml that is held a string
*
* @param xml the string that holds the Xml
* @return the root xml element
*/
public static FuncResult<XElement> parse(CharSequence xml, boolean keepwhitespace) {
return new XmlReader(new CharSequenceReader(xml), keepwhitespace).parse();
}
/*
* Read and parse xml that is pointed to by a reader (utf-8)
*
* @param in the reader that holds the Xml
* @return the root xml element
*/
public static FuncResult<XElement> parse(Reader in, boolean keepwhitespace) {
return new XmlReader(in, keepwhitespace).parse();
}
/*
* Read and parse xml that is pointed to by a stream (utf-8)
*
* @param in the stream that holds the Xml
* @return the root xml element
*/
public static FuncResult<XElement> parse(InputStream in, boolean keepwhitespace) {
return new XmlReader(new InputStreamReader(in), keepwhitespace).parse();
}
/*
* Read and parse xml that is held in memory (utf-8)
*
* @param mem the memory that holds the Xml
* @return the root xml element
*/
//public static FuncResult<XElement> parse(Memory mem, boolean keepwhitespace) {
// return new XmlReader(mem, keepwhitespace).parse();
//}
/*
* Read and parse an xml file
*
* @param fullname the file name to read and parse
* @return the root xml element
*/
public static FuncResult<XElement> loadFile(String fullname, boolean keepwhitespace) {
try {
return new XmlReader(new FileReader(fullname), keepwhitespace).parse();
}
catch (FileNotFoundException x) {
FuncResult<XElement> res = new FuncResult<XElement>();
res.exitTr(233, fullname);
return res;
}
}
/*
* Read and parse an xml file
*
* @param fl the file to read and parse
* @return the root xml element
*/
public static FuncResult<XElement> loadFile(File fl, boolean keepwhitespace) {
try {
return new XmlReader(new FileReader(fl), keepwhitespace).parse();
}
catch (FileNotFoundException x) {
FuncResult<XElement> res = new FuncResult<XElement>();
res.exitTr(233, fl.getPath());
return res;
}
}
/*
* Read and parse an xml file
*
* @param fl the file to read and parse
* @return the root xml element
*/
public static FuncResult<XElement> loadFile(Path fl, boolean keepwhitespace) {
try {
return new XmlReader(Files.newBufferedReader(fl), keepwhitespace).parse();
}
catch (FileNotFoundException x) {
FuncResult<XElement> res = new FuncResult<XElement>();
res.exitTr(233, fl);
return res;
}
catch (IOException x) {
FuncResult<XElement> res = new FuncResult<XElement>();
res.exit(1, "Error loading file: " + fl);
return res;
}
}
// instance
/**
* The source of the XML
*/
protected Reader input = null;
/**
* The root of the class structure
*/
protected XElement top = null;
/**
* The current element being worked on
*/
protected XElement element = null;
/**
* Holds all the parent elements of the current element
*/
protected Stack<XElement> stack = null;
protected boolean keepwhitespace = false;
/*
* Set XML source to be a Reader
*
* @param input the XML source to be parsed
*/
public XmlReader(Reader input, boolean keepwhitespace) {
this.input = input;
this.keepwhitespace = keepwhitespace;
}
/**
* Parses the XML and returns the root element. Comments and PI
* will be missing, this is a really basic and lightweight XML utility.
*
* @return the root XML element
*/
public FuncResult<XElement> parse() {
FuncResult<XElement> res = new FuncResult<XElement>();
this.top = null;
this.element = null;
this.stack = new Stack<XElement>();
OperationResult or = XmlParser.parse(this, this.input);
if (this.top == null)
res.errorTr(247);
if (!or.hasErrors())
res.setResult(this.top);
return res;
}
/*
* (non-Javadoc)
*
* @see IParseHandler#startDocument()
*/
@Override
public void startDocument(OperationResult or) {
this.top = null;
}
/*
* (non-Javadoc)
*
* @see IParseHandler#endDocument()
*/
@Override
public void endDocument(OperationResult or) {
}
/*
* (non-Javadoc)
*
* @see IParseHandler#startElement(java.lang.String, java.lang.String, java.util.Map, int, int)
*/
@Override
public void startElement(OperationResult or, String tag, Map<String, String> attributes, int line, int col) {
XElement newElement = new XElement(tag);
newElement.setLocation(line, col);
for (Map.Entry<String,String> entry : attributes.entrySet())
newElement.setRawAttribute((String) entry.getKey(),
(String) entry.getValue());
if (this.top == null)
this.top = newElement;
else
this.element.add(newElement);
if (this.element != null)
this.stack.push(this.element);
this.element = newElement;
}
/*
* (non-Javadoc)
*
* @see IParseHandler#element(java.lang.String, java.lang.String, java.util.Map, int, int)
*/
@Override
public void element(OperationResult or, String tag, Map<String, String> attributes, int line, int col) {
XElement newElement = new XElement(tag);
newElement.setLocation(line, col);
for (Map.Entry<String,String> entry : attributes.entrySet())
newElement.setRawAttribute((String) entry.getKey(),
(String) entry.getValue());
if (this.top == null)
this.top = newElement;
else
this.element.add(newElement);
}
/*
* (non-Javadoc)
*
* @see IParseHandler#endElement(java.lang.String, java.lang.String)
*/
@Override
public void endElement(OperationResult or, String tag) {
if (!this.stack.isEmpty())
this.element = this.stack.pop();
}
/*
* (non-Javadoc)
*
* @see IParseHandler#text(java.lang.String, boolean, int, int)
*/
@Override
public void text(OperationResult or, String str, boolean cdata, int line, int col) {
if (this.element == null)
return;
if (!this.keepwhitespace) {
str = str.trim();
// non-cdata text should not keep its extra whitespace
if (!cdata)
str = StringUtil.stripWhitespacePerXml(str);
if (StringUtil.isEmpty(str))
return;
}
XText text = new XText();
if (cdata)
text.setValue(str, true);
else
text.setRawValue(str);
this.element.add(text);
}
}