diff --git a/src/helma/objectmodel/db/XmlDatabase.java b/src/helma/objectmodel/db/XmlDatabase.java index 4af079d3..20af7296 100644 --- a/src/helma/objectmodel/db/XmlDatabase.java +++ b/src/helma/objectmodel/db/XmlDatabase.java @@ -12,7 +12,7 @@ import helma.objectmodel.dom.*; * A simple XML-database */ -public class XmlDatabase implements IDatabase { +public final class XmlDatabase implements IDatabase { private String dbHome; private File dbBaseDir; @@ -61,8 +61,8 @@ public class XmlDatabase implements IDatabase { if ( ! f.exists() ) throw new ObjectNotFoundException ("Object not found for key "+kstr+"."); try { - XmlReader reader = new XmlReader (nmgr); - Node node = (Node)reader.read (f, null); + XmlDatabaseReader reader = new XmlDatabaseReader (nmgr); + Node node = reader.read (f); return node; } catch ( RuntimeException x ) { nmgr.app.logEvent("error reading node from XmlDatbase: " + x.toString() ); diff --git a/src/helma/objectmodel/dom/XmlDatabaseReader.java b/src/helma/objectmodel/dom/XmlDatabaseReader.java new file mode 100644 index 00000000..0222ec75 --- /dev/null +++ b/src/helma/objectmodel/dom/XmlDatabaseReader.java @@ -0,0 +1,180 @@ +package helma.objectmodel.dom; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import java.text.SimpleDateFormat; +import java.text.ParseException; +import java.util.Date; +// import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; + +import javax.xml.parsers.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +import helma.objectmodel.INode; +import helma.objectmodel.db.DbKey; +import helma.objectmodel.db.ExternalizableVector; +import helma.objectmodel.db.Node; +import helma.objectmodel.db.NodeHandle; +import helma.objectmodel.db.NodeManager; +import helma.objectmodel.db.Property; +import helma.objectmodel.db.DbMapping; + +public final class XmlDatabaseReader extends DefaultHandler implements XmlConstants { + + private NodeManager nmgr = null; + private Node currentNode; + + private String elementType = null; + private String elementName = null; + private StringBuffer charBuffer = null; + + Hashtable propMap = null; + List subnodes = null; + + static SAXParserFactory factory = SAXParserFactory.newInstance (); + + + public XmlDatabaseReader (NodeManager nmgr) { + this.nmgr = nmgr; + } + + + /** + * read an InputSource with xml-content. + */ + public Node read (File file) + throws ParserConfigurationException, SAXException, IOException { + if (nmgr==null) + throw new RuntimeException ("can't create a new Node without a NodeManager"); + + SAXParser parser = factory.newSAXParser (); + + currentNode = null; + + parser.parse (file, this); + return currentNode; + } + + + public void startElement(String namespaceURI, String localName, String qName, Attributes atts) { + // 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 + if ("hopobject".equals (qName) && currentNode == null) { + String id = atts.getValue ("id"); + String name = atts.getValue ("name"); + String prototype = atts.getValue ("prototype"); + if ( "".equals(prototype) ) + prototype = "hopobject"; + + try { + long created = Long.parseLong (atts.getValue ("created")); + long lastmodified = Long.parseLong (atts.getValue ("lastModified")); + currentNode = new Node (name,id,prototype,nmgr.safe,created,lastmodified); + } catch ( NumberFormatException e ) { + currentNode = new Node (name,id,prototype,nmgr.safe); + } + return; + } + // find out what kind of element this is by looking at + // the number and names of attributes. + String idref = atts.getValue ("idref"); + if (idref != null) { + // a hopobject reference. + NodeHandle handle = makeNodeHandle (atts); + if ("hop:child".equals (qName)) { + if (subnodes == null) { + subnodes = new ExternalizableVector (); + currentNode.setSubnodes (subnodes); + } + subnodes.add (handle); + } else if ("hop:parent".equals (qName)) { + currentNode.setParentHandle (handle); + } else { + Property prop = new Property (qName, currentNode); + prop.setNodeHandle (handle); + if (propMap == null) { + propMap = new Hashtable (); + currentNode.setPropMap (propMap); + } + propMap.put (qName.toLowerCase(), prop); + } + } else { + // a primitive property + elementType = atts.getValue ("type"); + if (elementType == null) + elementType = "string"; + elementName = qName; + if (charBuffer == null) + charBuffer = new StringBuffer(); + else + charBuffer.setLength (0); + } + } + + public void characters (char[] ch, int start, int length) + throws SAXException { + // append chars to char buffer + if (elementType != null) + charBuffer.append (ch, start, length); + } + + public void endElement(String namespaceURI, String localName, String qName) + throws SAXException { + if (elementType != null) { + Property prop = new Property (elementName, currentNode); + String charValue = charBuffer.toString (); + charBuffer.setLength (0); + if ( "boolean".equals (elementType) ) { + if ( "true".equals(charValue) ) { + prop.setBooleanValue(true); + } else { + prop.setBooleanValue(false); + } + } else if ( "date".equals(elementType) ) { + SimpleDateFormat format = new SimpleDateFormat ( DATEFORMAT ); + try { + Date date = format.parse(charValue); + prop.setDateValue (date); + } catch ( ParseException e ) { + prop.setStringValue (charValue); + } + } else if ( "float".equals(elementType) ) { + prop.setFloatValue ((new Double(charValue)).doubleValue()); + } else if ( "integer".equals(elementType) ) { + prop.setIntegerValue ((new Long(charValue)).longValue()); + } else { + prop.setStringValue (charValue); + } + if (propMap == null) { + propMap = new Hashtable (); + currentNode.setPropMap (propMap); + } + propMap.put (elementName.toLowerCase(), prop); + elementName = null; + elementType = null; + charValue = null; + } + } + + + // create a node handle from a node reference DOM element + private NodeHandle makeNodeHandle (Attributes atts) { + String idref = atts.getValue ("idref"); + String protoref = atts.getValue ("prototyperef"); + DbMapping dbmap = null; + if (protoref != null) + dbmap = nmgr.getDbMapping(protoref); + return new NodeHandle (new DbKey (dbmap, idref)); + } + +} + diff --git a/src/helma/objectmodel/dom/XmlReader.java b/src/helma/objectmodel/dom/XmlReader.java index 6ad1f61e..ba3b7ccc 100644 --- a/src/helma/objectmodel/dom/XmlReader.java +++ b/src/helma/objectmodel/dom/XmlReader.java @@ -3,6 +3,7 @@ package helma.objectmodel.dom; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -10,37 +11,38 @@ import java.text.SimpleDateFormat; import java.text.ParseException; import java.util.Date; import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; +import java.util.Stack; -import org.w3c.dom.*; -import org.xml.sax.InputSource; +import javax.xml.parsers.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; import helma.objectmodel.INode; -import helma.objectmodel.db.DbKey; -import helma.objectmodel.db.ExternalizableVector; -import helma.objectmodel.db.Node; -import helma.objectmodel.db.NodeHandle; -import helma.objectmodel.db.NodeManager; -import helma.objectmodel.db.Property; -import helma.objectmodel.db.DbMapping; -public class XmlReader implements XmlConstants { +public final class XmlReader extends DefaultHandler implements XmlConstants { + private INode rootNode, currentNode; + private Stack nodeStack; private HashMap convertedNodes; - private NodeManager nmgr = null; + + private String elementType = null; + private String elementName = null; + private StringBuffer charBuffer = null; + + static SAXParserFactory factory = SAXParserFactory.newInstance (); + + boolean parsingHopObject; public XmlReader () { } - public XmlReader (NodeManager nmgr) { - this.nmgr = nmgr; - } /** * main entry to read an xml-file. */ - public INode read (File file, INode helmaNode) throws RuntimeException { + public INode read (File file, INode helmaNode) + throws ParserConfigurationException, SAXException, IOException { try { return read (new FileInputStream(file), helmaNode); } catch (FileNotFoundException notfound) { @@ -52,218 +54,159 @@ public class XmlReader implements XmlConstants { /** * read an InputStream with xml-content. */ - public INode read (InputStream in, INode helmaNode) throws RuntimeException { + 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 RuntimeException { + 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 RuntimeException { - if (helmaNode==null && nmgr==null) - throw new RuntimeException ("can't create a new Node without a NodeManager"); - Document document = XmlUtil.parse (in); - Element element = XmlUtil.getFirstElement(document); - if (element==null) - throw new RuntimeException ("corrupted xml-file"); + 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"); - if (helmaNode==null) { - return convert (element); + SAXParser parser = factory.newSAXParser (); + + rootNode = helmaNode; + currentNode = null; + convertedNodes = new HashMap (); + nodeStack = new Stack (); + parsingHopObject = true; + + parser.parse (in, this); + return rootNode; + } + + + 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); + currentNode = currentNode.createNode (qName); + } + // set the prototype on the current node and + // add it to the map of parsed nodes. + String name = atts.getValue ("name"); + String prototype = atts.getValue ("prototype"); + if ( !"".equals(prototype) && !"hopobject".equals(prototype) ) + currentNode.setPrototype (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 (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 + currentNode.setNode (qName, n); + } + } } else { - convertedNodes = new HashMap (); - INode convertedNode = convert (element, helmaNode); - convertedNodes = null; - return convertedNode; + // 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"; + elementName = qName; + if (charBuffer == null) + charBuffer = new StringBuffer(); + else + charBuffer.setLength (0); } } - /** - * convert children of an Element to a given helmaNode - */ - public INode convert (Element element, INode helmaNode) { - String idref = element.getAttribute("idref"); - String key = idref + "-" + element.getAttribute("prototyperef"); - if( idref!=null && !idref.equals("") ) { - if( convertedNodes.containsKey(key) ) { - return (INode)convertedNodes.get(key); - } - } - key = element.getAttribute("id") + "-" + element.getAttribute("prototype"); - convertedNodes.put( key, helmaNode ); - String prototype = element.getAttribute("prototype"); - if( !prototype.equals("") && !prototype.equals("hopobject") ) { - helmaNode.setPrototype( prototype ); - } - children(helmaNode, element); - return helmaNode; + 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); } - - // used by convert(Element,INode) - private INode children( INode helmaNode, Element element ) { - NodeList list = element.getChildNodes(); - int len = list.getLength(); - Element childElement; - for ( int i=0; i0 ) - helmaNode.setPropMap (propMap); + if (parsingHopObject && !nodeStack.isEmpty ()) + currentNode = (INode) nodeStack.pop (); else - helmaNode.setPropMap (null); - if ( subnodes.size()>0 ) - helmaNode.setSubnodes (subnodes); - else - helmaNode.setSubnodes (null); - return helmaNode; - } - - // create a node handle from a node reference DOM element - private NodeHandle makeNodeHandle (Element element) { - String idref = element.getAttribute("idref"); - String protoref = element.getAttribute("prototyperef"); - DbMapping dbmap = null; - if (protoref != null) - dbmap = nmgr.getDbMapping(protoref); - return new NodeHandle (new DbKey (dbmap, idref)); + parsingHopObject = true; // the next element end tag closes a hopobject again } }