/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2012 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.xml;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.jaamsim.render.RenderException;
import com.jaamsim.ui.LogBox;
/**
* A simple DOM like parser that handles arrays of white space separated numbers. This is used by both the COLLADA parser and the
* JaamSim mesh format parser
* @author matt.chudleigh
*
*/
public class XmlParser extends DefaultHandler{
private XmlNode rootNode;
private XmlNode currentNode;
private StringBuilder contentBuilder = new StringBuilder();
// The _nodeIDMap is a mapping of fragment IDs to nodes to make data analysis easier
private HashMap<String, XmlNode> nodeIDMap = new HashMap<>();
private List<String> doubleArrayTags;
private List<String> intArrayTags;
private List<String> stringArrayTags;
private List<String> booleanArrayTags;
private URL content;
public XmlParser(URL content) {
this.content = content;
rootNode = new XmlNode(null, "", "");
currentNode = rootNode;
}
/**
* Sets the list of tags that should have their contents parsed as an array of doubles
* @param arrayNames
*/
public void setDoubleArrayTags(List<String> arrayNames) {
doubleArrayTags = arrayNames;
}
/**
* Sets the list of tags that should have their contents parsed as an array of ints
* @param arrayNames
*/
public void setIntArrayTags(List<String> arrayNames) {
intArrayTags = arrayNames;
}
/**
* Sets the list of tags that should have their contents parsed as an array of booleans
* @param arrayNames
*/
public void setBooleanArrayTags(List<String> arrayNames) {
booleanArrayTags = arrayNames;
}
/**
* Sets the list of tags that should have their contents parsed as an array of strings
* @param arrayNames
*/
public void setStringArrayTags(List<String> arrayNames) {
stringArrayTags = arrayNames;
}
public void parse() {
InputStream in;
try {
in = content.openStream();
} catch (IOException ex) {
throw new RenderException("Can't read " + content);
}
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
try {
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(in, this);
} catch (Exception e) {
LogBox.renderLogException(e);
throw new RenderException(e.getMessage());
}
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
int IDIndex = attributes.getIndex("id");
String fragID = null;
if (IDIndex != -1) {
fragID = attributes.getValue(IDIndex);
}
XmlNode node = new XmlNode(currentNode, name, fragID);
currentNode.addChild(node);
currentNode = node;
for (int i = 0; i < attributes.getLength(); ++i) {
if (i == IDIndex) continue; // This attribute is special and handled
String n = attributes.getQName(i);
String v = attributes.getValue(i);
node.addAttrib(n, v);
}
if (IDIndex != -1) {
nodeIDMap.put(fragID, node);
}
contentBuilder.setLength(0);
}
@Override
public void characters(char [] ch, int start, int length) throws SAXException {
contentBuilder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
// Handle the contents type based on the current nodes tag
Object contents;
if (doubleArrayTags.contains(name)) {
contents = parseDoubleArray();
} else if (intArrayTags.contains(name)) {
contents = parseIntArray();
} else if (booleanArrayTags.contains(name)) {
contents = parseBooleanArray();
} else if (stringArrayTags.contains(name)) {
contents = parseStringArray();
} else {
contents = contentBuilder.toString().trim();
}
currentNode.setContent(contents);
currentNode = currentNode.getParent();
contentBuilder.setLength(0);
}
// return the number of 'words' in the contents
private int wordCount() {
int numWords = 0;
boolean readingWord = false;
for (int i = 0; i < contentBuilder.length(); ++i) {
char c = contentBuilder.charAt(i);
if (isWhitespace(c)) {
if (readingWord) {
// end of word
readingWord = false;
}
} else {
if (!readingWord) {
// beginning of word
readingWord = true;
++numWords;
}
}
}
return numWords;
}
private static final boolean isWhitespace(char c) {
return (c == ' ' || c == '\t' || c == '\n');
}
private int parsePos = 0;
private String getWord() {
StringBuilder ret = new StringBuilder();
while (parsePos < contentBuilder.length()) {
char c = contentBuilder.charAt(parsePos);
// Read through leading whitespace
if (!isWhitespace(c)) {
break;
}
++parsePos;
}
while (parsePos < contentBuilder.length()) {
char c = contentBuilder.charAt(parsePos++);
// Build up the 'word'
if (isWhitespace(c)) {
return ret.toString();
}
ret.append(c);
}
return ret.toString();
}
private double[] parseDoubleArray() {
int numWords = wordCount();
parsePos = 0;
double[] ret = new double[numWords];
for (int i = 0; i < numWords; ++i) {
String val = getWord();
ret[i] = Double.parseDouble(val);
}
return ret;
}
private int[] parseIntArray() {
int numWords = wordCount();
parsePos = 0;
int[] ret = new int[numWords];
for (int i = 0; i < numWords; ++i) {
String val = getWord();
ret[i] = Integer.parseInt(val);
}
return ret;
}
private boolean[] parseBooleanArray() {
int numWords = wordCount();
parsePos = 0;
boolean[] ret = new boolean[numWords];
for (int i = 0; i < numWords; ++i) {
String val = getWord();
ret[i] = Boolean.parseBoolean(val);
}
return ret;
}
private String[] parseStringArray() {
int numWords = wordCount();
parsePos = 0;
String[] ret = new String[numWords];
for (int i = 0; i < numWords; ++i) {
String val = getWord();
ret[i] = val;
}
return ret;
}
public XmlNode getRootNode() {
return rootNode;
}
public XmlNode getNodeByID(String id) {
return nodeIDMap.get(id);
}
}