/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author: root $
* $Revision: 8604 $
* $Date: 2007-09-28 15:16:38 +0200 (Fre, 28. Sep 2007) $
*/
package helma.objectmodel.dom;
import helma.objectmodel.INode;
import helma.objectmodel.db.WrappedNodeManager;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Stack;
/**
*
*/
public final class XmlReader extends DefaultHandler implements XmlConstants {
static SAXParserFactory factory = SAXParserFactory.newInstance();
private INode rootNode;
private INode currentNode;
private Stack nodeStack;
private HashMap convertedNodes;
private String elementType = null;
private String elementName = null;
private StringBuffer charBuffer = null;
boolean parsingHopObject;
WrappedNodeManager nmgr;
/**
* Creates a new XmlReader object.
*/
public XmlReader(WrappedNodeManager nmgr) {
this.nmgr = nmgr;
}
/**
* main entry to read an xml-file.
*/
public INode read(File file, INode helmaNode)
throws ParserConfigurationException, SAXException, IOException {
try {
return read(new FileInputStream(file), helmaNode);
} catch (FileNotFoundException notfound) {
System.err.println("couldn't find xml-file: " + file.getAbsolutePath());
return helmaNode;
}
}
/**
* read an InputStream with xml-content.
*/
public INode read(InputStream in, INode helmaNode)
throws ParserConfigurationException, SAXException, IOException {
return read(new InputSource(in), helmaNode);
}
/**
* read an character reader with xml-content.
*/
public INode read(Reader in, INode helmaNode)
throws ParserConfigurationException, SAXException, IOException {
return read(new InputSource(in), helmaNode);
}
/**
* read an InputSource with xml-content.
*/
public INode read(InputSource in, INode helmaNode)
throws ParserConfigurationException, SAXException, IOException {
if (helmaNode == null) {
throw new RuntimeException("Can't create a new Node without a root Node");
}
SAXParser parser = factory.newSAXParser();
rootNode = helmaNode;
currentNode = null;
convertedNodes = new HashMap();
nodeStack = new Stack();
parsingHopObject = true;
parser.parse(in, this);
return rootNode;
}
/**
*
*
* @param namespaceURI ...
* @param localName ...
* @param qName ...
* @param atts ...
*
* @throws SAXException ...
*/
public void startElement(String namespaceURI, String localName, String qName,
Attributes atts) throws SAXException {
// System.err.println ("XML-READ: startElement "+namespaceURI+", "+localName+", "+qName+", "+atts.getValue("id"));
// discard the first element called xmlroot
if ("xmlroot".equals(qName) && (currentNode == null)) {
return;
}
// if currentNode is null, this must be the hopobject node
String id = atts.getValue("id");
if (id != null) {
// check if there is a current node.
if (currentNode == null) {
// If currentNode is null, this is the root node we're parsing.
currentNode = rootNode;
} else if ("hop:child".equals(qName)) {
// it's an anonymous child node
nodeStack.push(currentNode);
currentNode = currentNode.createNode(null);
} else {
// it's a named node property
nodeStack.push(currentNode);
// property name may be encoded as "propertyname" attribute,
// otherwise it is the element name
String propName = atts.getValue("propertyname");
if (propName == null) {
propName = qName;
}
currentNode = currentNode.createNode(propName);
}
// set the prototype on the current node and
// add it to the map of parsed nodes.
String prototype = atts.getValue("prototype");
if (!"".equals(prototype) && !"hopobject".equals(prototype)) {
currentNode.setPrototype(prototype);
currentNode.setDbMapping(nmgr.getDbMapping(prototype));
}
String key = id + "-" + prototype;
convertedNodes.put(key, currentNode);
return;
}
// check if we have a currentNode to set properties on,
// otherwise throw exception.
if (currentNode == null) {
throw new SAXException("Invalid XML: No valid root HopObject found");
}
// check if we are inside a HopObject - otherwise throw an exception
if (!parsingHopObject) {
throw new SAXException("Invalid XML: Found nested non-HobObject elements");
}
// if we got so far, the element is not a hopobject. Set flag to prevent
// the hopobject stack to be popped when the element
// is closed.
parsingHopObject = false;
// Is it a reference to an already parsed node?
String idref = atts.getValue("idref");
if (idref != null) {
// a reference to a node that should have been parsed
// and lying in our cache of parsed nodes.
String prototyperef = atts.getValue("prototyperef");
String key = idref + "-" + prototyperef;
INode n = (INode) convertedNodes.get(key);
// if not a reference to a node we already read, try to
// resolve against the NodeManager.
if (n == null) {
n = nmgr.getNode(idref, nmgr.getDbMapping(prototyperef));
}
if (n != null) {
if ("hop:child".equals(qName)) {
// add an already parsed node as child to current node
currentNode.addNode(n);
} else {
// set an already parsed node as node property to current node
// property name may be encoded as "propertyname" attribute,
// otherwise it is the element name
String propName = atts.getValue("propertyname");
if (propName == null) {
propName = qName;
}
if ("hop:parent".equals(qName)) {
// FIXME: we ought to set parent here, but we're
// dealing with INodes, which don't have a setParent().
} else {
currentNode.setNode(propName, n);
}
}
}
} else {
// It's a primitive property. Remember the property name and type
// so we can properly parse/interpret the character data when we
// get it later on.
elementType = atts.getValue("type");
if (elementType == null) {
elementType = "string";
}
// property name may be encoded as "propertyname" attribute,
// otherwise it is the element name
elementName = atts.getValue("propertyname");
if (elementName == null) {
elementName = qName;
}
if (charBuffer == null) {
charBuffer = new StringBuffer();
} else {
charBuffer.setLength(0);
}
}
}
/**
*
*
* @param ch ...
* @param start ...
* @param length ...
*
* @throws SAXException ...
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
// System.err.println ("CHARACTERS: "+new String (ch, start, length));
// append chars to char buffer
if (elementType != null) {
charBuffer.append(ch, start, length);
}
}
/**
*
*
* @param namespaceURI ...
* @param localName ...
* @param qName ...
*
* @throws SAXException ...
*/
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
if (elementType != null) {
String charValue = charBuffer.toString();
charBuffer.setLength(0);
if ("boolean".equals(elementType)) {
if ("true".equals(charValue)) {
currentNode.setBoolean(elementName, true);
} else {
currentNode.setBoolean(elementName, false);
}
} else if ("date".equals(elementType)) {
SimpleDateFormat format = new SimpleDateFormat(DATEFORMAT);
try {
Date date = format.parse(charValue);
currentNode.setDate(elementName, date);
} catch (ParseException e) {
currentNode.setString(elementName, charValue);
}
} else if ("float".equals(elementType)) {
currentNode.setFloat(elementName, (new Double(charValue)).doubleValue());
} else if ("integer".equals(elementType)) {
currentNode.setInteger(elementName, (new Long(charValue)).longValue());
} else {
currentNode.setString(elementName, charValue);
}
elementName = null;
elementType = null;
charValue = null;
}
if (parsingHopObject && !nodeStack.isEmpty()) {
currentNode = (INode) nodeStack.pop();
} else {
parsingHopObject = true; // the next element end tag closes a hopobject again
}
}
}