/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.io; import java.io.IOException; import java.io.LineNumberReader; import java.util.ArrayList; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; import com.vividsolutions.jump.feature.AttributeType; import com.vividsolutions.jump.feature.FeatureSchema; import com.vividsolutions.jump.util.FlexibleDateParser; /** * Reads an XML file that starts with a 'JCSGMLInputTemplate'. <br> * Will abort read at the end of the 'JCSGMLInputTemplate' tag. <br> * Constructs a description of the Columns and geometry tag so the <br> * actual GML parser ({@link GMLReader}) will know what to do with different tags. *<br><Br> *This is a SAX Handler. */ public class GMLInputTemplate extends DefaultHandler { LineNumberReader myReader; XMLReader xr; String tagBody = ""; String collectionTag; String featureTag; private ArrayList geometryElements = new ArrayList(20); //shouldnt need more than 20, but will auto-expand bigger String streamName; boolean havecollectionTag = false; boolean havefeatureTag = false; boolean havegeometryElement = false; public boolean loaded = false; ArrayList columnDefinitions = new ArrayList(); //list of type ColumnDescription //for the jcs column definition int columnDef_valueType = 0; // 0 - undef, 1 = body, 2 = attribute String columnDef_valueAttribute = ""; // name of the attribute the value is in String columnDef_tagName = ""; // tag this is a part of int columnDef_tagType = 0; // 0 - undef, 1=tag only, 2 = attribute, 3 = att & value String columnDef_tagAttribute = ""; String columnDef_tagValue = ""; String columnDef_columnName = ""; com.vividsolutions.jump.feature.AttributeType columnDef_type = null; String lastStartTag_uri; String lastStartTag_name; String lastStartTag_qName; Attributes lastStartTag_atts; /** * constructor - makes a new org.apache.xerces.parser and makes this class be the SAX * content and error handler. */ public GMLInputTemplate() { super(); xr = new org.apache.xerces.parsers.SAXParser(); xr.setContentHandler(this); xr.setErrorHandler(this); } /** * Returns the column name for the 'index'th column. *@param index 0=first */ public String columnName(int index) throws ParseException { if (loaded) { return ((ColumnDescription) columnDefinitions.get(index)).columnName; } else { throw new ParseException( "requested columnName w/o loading the template"); } } /** * Converts this GMLInputTemplate to a feature schema. **/ public FeatureSchema toFeatureSchema() throws ParseException { if (!(loaded)) { throw new ParseException( "requested toFeatureSchema w/o loading the template"); } FeatureSchema fcmd = new FeatureSchema(); fcmd.addAttribute("GEOMETRY", AttributeType.GEOMETRY); for (int t = 0; t < columnDefinitions.size(); t++) { fcmd.addAttribute(((ColumnDescription) columnDefinitions.get(t)).columnName, ((ColumnDescription) columnDefinitions.get(t)).getType()); } return (fcmd); } /** * Function to help the GMLParser - is this tag name the Geometry Element tag name? *@param tag an XML tag name **/ public boolean isGeometryElement(String tag) { int t; String s; for (t = 0; t < geometryElements.size(); t++) { s = (String) geometryElements.get(t); if (s.equalsIgnoreCase(tag)) { return true; } } return false; } /** * Helper function - load a GMLInputTemplate file with the stream name "Unknown Stream" */ public void load(java.io.Reader r) throws ParseException, IOException { load(r, "Unknown Stream"); } /** * Main function - load in an XML file. <br> * Error handling/reporting also done here. *@param r where to read the XML file from *@param readerName name of the stream for error reporting */ public void load(java.io.Reader r, String readerName) throws ParseException, IOException { myReader = new LineNumberReader(r); streamName = readerName; // for error reporting try { xr.parse(new InputSource(myReader)); } catch (EndOfParseException e) { // This is not really an error } catch (SAXParseException e) { throw new ParseException(e.getMessage() + " (Is this really a GML file?) Last Opened Tag: " + lastStartTag_qName + ". Reader reports last line read as " + myReader.getLineNumber(), streamName + " - " + e.getPublicId() + " (" + e.getSystemId() + ") ", e.getLineNumber(), e.getColumnNumber()); } catch (SAXException e) { throw new ParseException(e.getMessage() + " Last Opened Tag: " + lastStartTag_qName, streamName, myReader.getLineNumber(), 0); } loaded = (havecollectionTag) && (havefeatureTag) && (havegeometryElement); if (!(loaded)) { String miss; miss = ""; if (!(havecollectionTag)) { miss = miss + "Missing CollectionElement. "; } if (!(havefeatureTag)) { miss = miss + "Missing FeatureElement. "; } if (!(havegeometryElement)) { miss = miss + "Missing GeometryElement. "; } throw new ParseException("Failed to load the GML Input Template. " + miss); } } /** * Get the name of the FeatureCollectionElement tag */ public String getFeatureCollectionElementName() throws ParseException { if (loaded) { return collectionTag; } else { throw new ParseException( "requested FeatureCollectionElementName w/o loading the template"); } } /** * Get the name of the FeatureElement tag */ public String getFeatureElementName() throws ParseException { if (loaded) { return featureTag; } else { throw new ParseException( "requested FeatureCollectionElementName w/o loading the template"); } } /** * Given a tag name and its XML attributes, find the index of the column it belongs to.<br> * Returns -1 if it doesnt match any of the columns. *@param XMLtagName the tag name found in the xml *@param xmlAtts the attributes associated with the xml */ public int match(String XMLtagName, Attributes xmlAtts) throws ParseException { if (loaded) { for (int t = 0; t < columnDefinitions.size(); t++) { if (((ColumnDescription) columnDefinitions.get(t)).match( XMLtagName, xmlAtts) != 0) { return t; } } return -1; } throw new ParseException("requested match() w/o loading the template"); } /** * Given a ColumnDescription index, the XML tagBody, and the tag's attributes, return the * actual value (it could be an attribute or the tag's body). You probably got the index * from the match() function. * *@param index index number of the column description *@param tagBody value of the XML tag body *@param xmlAtts key/values of the XML tag's attributes **/ public Object getColumnValue(int index, String tagBody, Attributes xmlAtts) throws ParseException { String val; ColumnDescription cd; if (!(loaded)) { throw new ParseException( "requested getColumnValue w/o loading the template"); } if (((ColumnDescription) columnDefinitions.get(index)).valueType == ColumnDescription.VALUE_IS_BODY) { val = tagBody; } else { val = xmlAtts.getValue(((ColumnDescription) columnDefinitions.get( index)).valueAttribute); } //have the value as a string, make it an object cd = (ColumnDescription) columnDefinitions.get(index); if (cd.type == AttributeType.STRING) { return val; } if (cd.type == AttributeType.INTEGER) { try { //Was Long, but JUMP expects AttributeType.INTEGER to hold Integers. //e.g. open JML file then save as Shapefile => get ClassCastException. //Dave Blasby says there was a reason for changing it to Long, but //can't remember -- suspects there were datasets whose INTEGER //values didn't fit in an Integer. [Jon Aquino 1/13/2004] //Compromise -- try Long if Integer fails. Some other parts of JUMP //won't like it (exceptions), but it's better than null. Actually I don't like //this null business -- future: warn the user. [Jon Aquino 1/13/2004] try { return new Integer(val); } catch (Exception e) { return new Long(val); } } catch (Exception e) { return null; } } if (cd.type == AttributeType.DOUBLE) { try { return new Double(val); } catch (Exception e) { return null; } } //Adding date support. Can we throw an exception if an exception //occurs or if the type is unrecognized? [Jon Aquino] if (cd.type == AttributeType.DATE) { try { return dateParser.parse(val, false); } catch (Exception e) { return null; } } if (cd.type == AttributeType.OBJECT) { return val; // the GML file has text in it and we want to convert it to an "object" // just return a String since we dont know anything else about it! } return null; //unknown type } private FlexibleDateParser dateParser = new FlexibleDateParser(); //////////////////////////////////////////////////////////////////// // Error handlers. //////////////////////////////////////////////////////////////////// public void warning(SAXParseException exception) throws SAXException { throw exception; } public void error(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } //////////////////////////////////////////////////////////////////// // Event handlers. //////////////////////////////////////////////////////////////////// /** * SAX startDocument handler - null */ public void startDocument() { //System.out.println("Start document"); } /** * SAX endDocument handler - null */ public void endDocument() { //System.out.println("End document"); } /** * SAX startElement handler <br> * Basically just records the tag name and its attributes since all the * smarts are in the endElement handler. */ public void startElement(String uri, String name, String qName, Attributes atts) throws SAXException { try { tagBody = ""; if (qName.equals("column")) { //reset these values! columnDef_tagName = ""; // tag this is a part of columnDef_tagType = 0; // 0 - undef, 1=tag only, 2 = attribute, 3 = att & value columnDef_tagAttribute = ""; columnDef_tagValue = ""; columnDef_valueType = 0; // 0 - undef, 1 = body, 2 = attribute columnDef_valueAttribute = ""; // name of the attribute the value is in columnDef_columnName = ""; columnDef_type = null; } lastStartTag_uri = uri; lastStartTag_name = name; lastStartTag_qName = qName; lastStartTag_atts = atts; } catch (Exception e) { throw new SAXException(e.getMessage()); } } /** * Helper function - get attribute in a case insensitive manner. * returns index or -1 if not found. *@param atts the attributes for the xml tag (from SAX) *@param att_name the name of the attribute to search for */ int lookupAttribute(Attributes atts, String att_name) { int t; for (t = 0; t < atts.getLength(); t++) { if (atts.getQName(t).equalsIgnoreCase(att_name)) { return t; } } return -1; } /** * SAX endElement handler - the main working function <br> * <br> * handles the following tags in the appropriate manner: <br> * GeometryElement : sets the name of the document's geometry tag <bR> * CollectionElement : sets the name of the document's collection tag<br> * FeatureElement : sets the name of the document's feature tag<br> * type : sets a column type (to be used when a column ends) <br> * valueelement : sets information about what element a column is associated with <br> * valuelocation : set information about where a column's value is stored in the document <br> * column : takes the accumlated information about a column and constructs a ColumnDescription object <bR> */ public void endElement(String uri, String name, String qName) throws SAXException { try { if (qName.equalsIgnoreCase("JCSGMLInputTemplate")) { throw new EndOfParseException("Finished parsing input template"); } if (qName.equalsIgnoreCase("type")) { String t; t = tagBody.toUpperCase(); t = t.trim(); try { columnDef_type = com.vividsolutions.jump.feature.AttributeType.toAttributeType(t); } catch (IllegalArgumentException e) { //Hmm...we're just eating the exception here. Perhaps we should //allow the exception to propagate up to the caller. [Jon Aquino] columnDef_type = null; } } if (qName.equalsIgnoreCase("GeometryElement")) { tagBody = tagBody.trim(); geometryElements.add(new String(tagBody)); havegeometryElement = true; return; } if (qName.equalsIgnoreCase("CollectionElement")) { tagBody = tagBody.trim(); collectionTag = tagBody; havecollectionTag = true; return; } if (qName.equalsIgnoreCase("FeatureElement")) { tagBody = tagBody.trim(); featureTag = tagBody; havefeatureTag = true; return; } if (qName.equalsIgnoreCase("name")) { columnDef_columnName = tagBody.trim(); } if (qName.equalsIgnoreCase("valueelement")) { int attindex; columnDef_tagType = 1; //attindex = lastStartTag_atts.getIndex("elementname"); attindex = lookupAttribute(lastStartTag_atts, "elementname"); if (attindex == -1) { throw new SAXException( "column definition has 'valueelement' tag without 'elementname' attribute"); } columnDef_tagName = new String(lastStartTag_atts.getValue( attindex)); //attindex = lastStartTag_atts.getIndex("attributename"); attindex = lookupAttribute(lastStartTag_atts, "attributename"); if (attindex != -1) { columnDef_tagAttribute = new String(lastStartTag_atts.getValue( attindex)); columnDef_tagType = 2; //attindex = lastStartTag_atts.getIndex("attributevalue"); attindex = lookupAttribute(lastStartTag_atts, "attributevalue"); if (attindex != -1) { columnDef_tagValue = new String(lastStartTag_atts.getValue( attindex)); columnDef_tagType = 3; } } } if (qName.equalsIgnoreCase("valuelocation")) { int attindex; //attindex = lastStartTag_atts.getIndex("position"); attindex = lookupAttribute(lastStartTag_atts, "position"); if (attindex == -1) { throw new SAXException( "column definition has 'valuelocation' tag without 'position' attribute"); } if (lastStartTag_atts.getValue(attindex).equalsIgnoreCase("body")) { columnDef_valueType = 1; } else { //attindex = lastStartTag_atts.getIndex("attributename"); attindex = lookupAttribute(lastStartTag_atts, "attributename"); columnDef_valueType = 2; if (attindex == -1) { throw new SAXException( "column definition has 'valuelocation' tag, attribute type, but no 'attributename' attribute"); } columnDef_valueAttribute = new String(lastStartTag_atts.getValue( attindex)); } } if (qName.equalsIgnoreCase("column")) { //commit column entry if (columnDef_tagName.equalsIgnoreCase("")) { throw new SAXException( "column Definition didnt include tag name ('<name>...</name>')"); } if (columnDef_tagType == 0) { throw new SAXException( "column Definition didnt include 'valueelement' "); } if (columnDef_valueType == 0) { throw new SAXException( "column Definition didnt have a 'valuelocation'"); } //we're okay ColumnDescription colDes; colDes = new ColumnDescription(); colDes.setColumnName(columnDef_columnName); if (colDes.columnName.compareTo("GEOMETRY") == 0) { throw new ParseException( "Cannot have a column named GEOMETRY!"); } if (columnDef_valueType == 2) //auto set for #1=body { colDes.setValueAttribute(columnDef_valueAttribute); //not the body } colDes.setTagName(columnDef_tagName); if (columnDef_tagType == 3) //1=simple { colDes.setTagAttribute(columnDef_tagAttribute, columnDef_tagValue); } if (columnDef_tagType == 2) { colDes.setTagAttribute(columnDef_tagAttribute); } colDes.setType(columnDef_type); columnDefinitions.add(colDes); //remember this } } catch (EndOfParseException e) { throw e; } catch (Exception e) { throw new SAXException(e.getMessage()); } } /** *SAX handler for characters - just store and accumulate for later use */ public void characters(char[] ch, int start, int length) throws SAXException { try { String part; part = new String(ch, start, length); tagBody = tagBody + part; } catch (Exception e) { throw new SAXException(e.getMessage()); } } }