001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.io;
034:
035: import java.io.IOException;
036: import java.io.LineNumberReader;
037: import java.util.ArrayList;
038:
039: import org.xml.sax.*;
040: import org.xml.sax.helpers.DefaultHandler;
041:
042: import com.vividsolutions.jump.feature.AttributeType;
043: import com.vividsolutions.jump.feature.FeatureSchema;
044: import com.vividsolutions.jump.util.FlexibleDateParser;
045:
046: /**
047: * Reads an XML file that starts with a 'JCSGMLInputTemplate'. <br>
048: * Will abort read at the end of the 'JCSGMLInputTemplate' tag. <br>
049: * Constructs a description of the Columns and geometry tag so the <br>
050: * actual GML parser ({@link GMLReader}) will know what to do with different tags.
051: *<br><Br>
052: *This is a SAX Handler.
053: */
054: public class GMLInputTemplate extends DefaultHandler {
055: LineNumberReader myReader;
056: XMLReader xr;
057: String tagBody = "";
058: String collectionTag;
059: String featureTag;
060: private ArrayList geometryElements = new ArrayList(20); //shouldnt need more than 20, but will auto-expand bigger
061: String streamName;
062: boolean havecollectionTag = false;
063: boolean havefeatureTag = false;
064: boolean havegeometryElement = false;
065: public boolean loaded = false;
066: ArrayList columnDefinitions = new ArrayList(); //list of type ColumnDescription
067:
068: //for the jcs column definition
069: int columnDef_valueType = 0; // 0 - undef, 1 = body, 2 = attribute
070: String columnDef_valueAttribute = ""; // name of the attribute the value is in
071: String columnDef_tagName = ""; // tag this is a part of
072: int columnDef_tagType = 0; // 0 - undef, 1=tag only, 2 = attribute, 3 = att & value
073: String columnDef_tagAttribute = "";
074: String columnDef_tagValue = "";
075: String columnDef_columnName = "";
076: com.vividsolutions.jump.feature.AttributeType columnDef_type = null;
077: String lastStartTag_uri;
078: String lastStartTag_name;
079: String lastStartTag_qName;
080: Attributes lastStartTag_atts;
081:
082: /**
083: * constructor - makes a new org.apache.xerces.parser and makes this class be the SAX
084: * content and error handler.
085: */
086: public GMLInputTemplate() {
087: super ();
088: xr = new org.apache.xerces.parsers.SAXParser();
089: xr.setContentHandler(this );
090: xr.setErrorHandler(this );
091: }
092:
093: /**
094: * Returns the column name for the 'index'th column.
095: *@param index 0=first
096: */
097: public String columnName(int index) throws ParseException {
098: if (loaded) {
099: return ((ColumnDescription) columnDefinitions.get(index)).columnName;
100: } else {
101: throw new ParseException(
102: "requested columnName w/o loading the template");
103: }
104: }
105:
106: /**
107: * Converts this GMLInputTemplate to a feature schema.
108: **/
109: public FeatureSchema toFeatureSchema() throws ParseException {
110: if (!(loaded)) {
111: throw new ParseException(
112: "requested toFeatureSchema w/o loading the template");
113: }
114:
115: FeatureSchema fcmd = new FeatureSchema();
116:
117: fcmd.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
118:
119: for (int t = 0; t < columnDefinitions.size(); t++) {
120: fcmd.addAttribute(((ColumnDescription) columnDefinitions
121: .get(t)).columnName,
122: ((ColumnDescription) columnDefinitions.get(t))
123: .getType());
124: }
125:
126: return (fcmd);
127: }
128:
129: /**
130: * Function to help the GMLParser - is this tag name the Geometry Element tag name?
131: *@param tag an XML tag name
132: **/
133: public boolean isGeometryElement(String tag) {
134: int t;
135: String s;
136:
137: for (t = 0; t < geometryElements.size(); t++) {
138: s = (String) geometryElements.get(t);
139:
140: if (s.equalsIgnoreCase(tag)) {
141: return true;
142: }
143: }
144:
145: return false;
146: }
147:
148: /**
149: * Helper function - load a GMLInputTemplate file with the stream name "Unknown Stream"
150: */
151: public void load(java.io.Reader r) throws ParseException,
152: IOException {
153: load(r, "Unknown Stream");
154: }
155:
156: /**
157: * Main function - load in an XML file. <br>
158: * Error handling/reporting also done here.
159: *@param r where to read the XML file from
160: *@param readerName name of the stream for error reporting
161: */
162: public void load(java.io.Reader r, String readerName)
163: throws ParseException, IOException {
164: myReader = new LineNumberReader(r);
165: streamName = readerName; // for error reporting
166:
167: try {
168: xr.parse(new InputSource(myReader));
169: } catch (EndOfParseException e) {
170: // This is not really an error
171: } catch (SAXParseException e) {
172: throw new ParseException(
173: e.getMessage()
174: + " (Is this really a GML file?) Last Opened Tag: "
175: + lastStartTag_qName
176: + ". Reader reports last line read as "
177: + myReader.getLineNumber(), streamName
178: + " - " + e.getPublicId() + " ("
179: + e.getSystemId() + ") ",
180: e.getLineNumber(), e.getColumnNumber());
181: } catch (SAXException e) {
182: throw new ParseException(e.getMessage()
183: + " Last Opened Tag: " + lastStartTag_qName,
184: streamName, myReader.getLineNumber(), 0);
185: }
186:
187: loaded = (havecollectionTag) && (havefeatureTag)
188: && (havegeometryElement);
189:
190: if (!(loaded)) {
191: String miss;
192: miss = "";
193:
194: if (!(havecollectionTag)) {
195: miss = miss + "Missing CollectionElement. ";
196: }
197:
198: if (!(havefeatureTag)) {
199: miss = miss + "Missing FeatureElement. ";
200: }
201:
202: if (!(havegeometryElement)) {
203: miss = miss + "Missing GeometryElement. ";
204: }
205:
206: throw new ParseException(
207: "Failed to load the GML Input Template. " + miss);
208: }
209: }
210:
211: /**
212: * Get the name of the FeatureCollectionElement tag
213: */
214: public String getFeatureCollectionElementName()
215: throws ParseException {
216: if (loaded) {
217: return collectionTag;
218: } else {
219: throw new ParseException(
220: "requested FeatureCollectionElementName w/o loading the template");
221: }
222: }
223:
224: /**
225: * Get the name of the FeatureElement tag
226: */
227: public String getFeatureElementName() throws ParseException {
228: if (loaded) {
229: return featureTag;
230: } else {
231: throw new ParseException(
232: "requested FeatureCollectionElementName w/o loading the template");
233: }
234: }
235:
236: /**
237: * Given a tag name and its XML attributes, find the index of the column it belongs to.<br>
238: * Returns -1 if it doesnt match any of the columns.
239: *@param XMLtagName the tag name found in the xml
240: *@param the attributes associated with the xml
241: */
242: public int match(String XMLtagName, Attributes xmlAtts)
243: throws ParseException {
244: if (loaded) {
245: for (int t = 0; t < columnDefinitions.size(); t++) {
246: if (((ColumnDescription) columnDefinitions.get(t))
247: .match(XMLtagName, xmlAtts) != 0) {
248: return t;
249: }
250: }
251:
252: return -1;
253: }
254:
255: throw new ParseException(
256: "requested match() w/o loading the template");
257: }
258:
259: /**
260: * Given a ColumnDescription index, the XML tagBody, and the tag's attributes, return the
261: * actual value (it could be an attribute or the tag's body). You probably got the index
262: * from the match() function.
263: *
264: *@param index index number of the column description
265: *@param tagBody value of the XML tag body
266: *@param xmlAtts key/values of the XML tag's attributes
267: **/
268: public Object getColumnValue(int index, String tagBody,
269: Attributes xmlAtts) throws ParseException {
270: String val;
271: ColumnDescription cd;
272:
273: if (!(loaded)) {
274: throw new ParseException(
275: "requested getColumnValue w/o loading the template");
276: }
277:
278: if (((ColumnDescription) columnDefinitions.get(index)).valueType == ColumnDescription.VALUE_IS_BODY) {
279: val = tagBody;
280: } else {
281: val = xmlAtts
282: .getValue(((ColumnDescription) columnDefinitions
283: .get(index)).valueAttribute);
284: }
285:
286: //have the value as a string, make it an object
287: cd = (ColumnDescription) columnDefinitions.get(index);
288:
289: if (cd.type == AttributeType.STRING) {
290: return val;
291: }
292:
293: if (cd.type == AttributeType.INTEGER) {
294: try {
295: //Was Long, but JUMP expects AttributeType.INTEGER to hold Integers.
296: //e.g. open JML file then save as Shapefile => get ClassCastException.
297: //Dave Blasby says there was a reason for changing it to Long, but
298: //can't remember -- suspects there were datasets whose INTEGER
299: //values didn't fit in an Integer. [Jon Aquino 1/13/2004]
300:
301: //Compromise -- try Long if Integer fails. Some other parts of JUMP
302: //won't like it (exceptions), but it's better than null. Actually I don't like
303: //this null business -- future: warn the user. [Jon Aquino 1/13/2004]
304: try {
305: return new Integer(val);
306: } catch (Exception e) {
307: return new Long(val);
308: }
309: } catch (Exception e) {
310: return null;
311: }
312: }
313:
314: if (cd.type == AttributeType.DOUBLE) {
315: try {
316: return new Double(val);
317: } catch (Exception e) {
318: return null;
319: }
320: }
321:
322: //Adding date support. Can we throw an exception if an exception
323: //occurs or if the type is unrecognized? [Jon Aquino]
324: if (cd.type == AttributeType.DATE) {
325: try {
326: return dateParser.parse(val, false);
327: } catch (Exception e) {
328: return null;
329: }
330: }
331:
332: if (cd.type == AttributeType.OBJECT) {
333: return val; // the GML file has text in it and we want to convert it to an "object"
334: // just return a String since we dont know anything else about it!
335: }
336:
337: return null; //unknown type
338: }
339:
340: private FlexibleDateParser dateParser = new FlexibleDateParser();
341:
342: ////////////////////////////////////////////////////////////////////
343: // Error handlers.
344: ////////////////////////////////////////////////////////////////////
345: public void warning(SAXParseException exception)
346: throws SAXException {
347: throw exception;
348: }
349:
350: public void error(SAXParseException exception) throws SAXException {
351: throw exception;
352: }
353:
354: public void fatalError(SAXParseException exception)
355: throws SAXException {
356: throw exception;
357: }
358:
359: ////////////////////////////////////////////////////////////////////
360: // Event handlers.
361: ////////////////////////////////////////////////////////////////////
362:
363: /**
364: * SAX startDocument handler - null
365: */
366: public void startDocument() {
367: //System.out.println("Start document");
368: }
369:
370: /**
371: * SAX endDocument handler - null
372: */
373: public void endDocument() {
374: //System.out.println("End document");
375: }
376:
377: /**
378: * SAX startElement handler <br>
379: * Basically just records the tag name and its attributes since all the
380: * smarts are in the endElement handler.
381: */
382: public void startElement(String uri, String name, String qName,
383: Attributes atts) throws SAXException {
384: try {
385: tagBody = "";
386:
387: if (qName.equals("column")) {
388: //reset these values!
389: columnDef_tagName = ""; // tag this is a part of
390: columnDef_tagType = 0; // 0 - undef, 1=tag only, 2 = attribute, 3 = att & value
391: columnDef_tagAttribute = "";
392: columnDef_tagValue = "";
393:
394: columnDef_valueType = 0; // 0 - undef, 1 = body, 2 = attribute
395: columnDef_valueAttribute = ""; // name of the attribute the value is in
396:
397: columnDef_columnName = "";
398: columnDef_type = null;
399: }
400:
401: lastStartTag_uri = uri;
402: lastStartTag_name = name;
403: lastStartTag_qName = qName;
404: lastStartTag_atts = atts;
405: } catch (Exception e) {
406: throw new SAXException(e.getMessage());
407: }
408: }
409:
410: /**
411: * Helper function - get attribute in a case insensitive manner.
412: * returns index or -1 if not found.
413: *@param atts the attributes for the xml tag (from SAX)
414: *@param att_name the name of the attribute to search for
415: */
416: int lookupAttribute(Attributes atts, String att_name) {
417: int t;
418:
419: for (t = 0; t < atts.getLength(); t++) {
420: if (atts.getQName(t).equalsIgnoreCase(att_name)) {
421: return t;
422: }
423: }
424:
425: return -1;
426: }
427:
428: /**
429: * SAX endElement handler - the main working function <br>
430: * <br>
431: * handles the following tags in the appropriate manner: <br>
432: * GeometryElement : sets the name of the document's geometry tag <bR>
433: * CollectionElement : sets the name of the document's collection tag<br>
434: * FeatureElement : sets the name of the document's feature tag<br>
435: * type : sets a column type (to be used when a column ends) <br>
436: * valueelement : sets information about what element a column is associated with <br>
437: * valuelocation : set information about where a column's value is stored in the document <br>
438: * column : takes the accumlated information about a column and constructs a ColumnDescription object <bR>
439: */
440: public void endElement(String uri, String name, String qName)
441: throws SAXException {
442: try {
443: if (qName.equalsIgnoreCase("JCSGMLInputTemplate")) {
444: throw new EndOfParseException(
445: "Finished parsing input template");
446: }
447:
448: if (qName.equalsIgnoreCase("type")) {
449: String t;
450: t = tagBody.toUpperCase();
451: t = t.trim();
452:
453: try {
454: columnDef_type = com.vividsolutions.jump.feature.AttributeType
455: .toAttributeType(t);
456: } catch (IllegalArgumentException e) {
457: //Hmm...we're just eating the exception here. Perhaps we should
458: //allow the exception to propagate up to the caller. [Jon Aquino]
459: columnDef_type = null;
460: }
461: }
462:
463: if (qName.equalsIgnoreCase("GeometryElement")) {
464: tagBody = tagBody.trim();
465: geometryElements.add(new String(tagBody));
466: havegeometryElement = true;
467:
468: return;
469: }
470:
471: if (qName.equalsIgnoreCase("CollectionElement")) {
472: tagBody = tagBody.trim();
473: collectionTag = tagBody;
474: havecollectionTag = true;
475:
476: return;
477: }
478:
479: if (qName.equalsIgnoreCase("FeatureElement")) {
480: tagBody = tagBody.trim();
481: featureTag = tagBody;
482: havefeatureTag = true;
483:
484: return;
485: }
486:
487: if (qName.equalsIgnoreCase("name")) {
488: columnDef_columnName = tagBody.trim();
489: }
490:
491: if (qName.equalsIgnoreCase("valueelement")) {
492: int attindex;
493:
494: columnDef_tagType = 1;
495:
496: //attindex = lastStartTag_atts.getIndex("elementname");
497: attindex = lookupAttribute(lastStartTag_atts,
498: "elementname");
499:
500: if (attindex == -1) {
501: throw new SAXException(
502: "column definition has 'valueelement' tag without 'elementname' attribute");
503: }
504:
505: columnDef_tagName = new String(lastStartTag_atts
506: .getValue(attindex));
507:
508: //attindex = lastStartTag_atts.getIndex("attributename");
509: attindex = lookupAttribute(lastStartTag_atts,
510: "attributename");
511:
512: if (attindex != -1) {
513: columnDef_tagAttribute = new String(
514: lastStartTag_atts.getValue(attindex));
515: columnDef_tagType = 2;
516:
517: //attindex = lastStartTag_atts.getIndex("attributevalue");
518: attindex = lookupAttribute(lastStartTag_atts,
519: "attributevalue");
520:
521: if (attindex != -1) {
522: columnDef_tagValue = new String(
523: lastStartTag_atts.getValue(attindex));
524: columnDef_tagType = 3;
525: }
526: }
527: }
528:
529: if (qName.equalsIgnoreCase("valuelocation")) {
530: int attindex;
531:
532: //attindex = lastStartTag_atts.getIndex("position");
533: attindex = lookupAttribute(lastStartTag_atts,
534: "position");
535:
536: if (attindex == -1) {
537: throw new SAXException(
538: "column definition has 'valuelocation' tag without 'position' attribute");
539: }
540:
541: if (lastStartTag_atts.getValue(attindex)
542: .equalsIgnoreCase("body")) {
543: columnDef_valueType = 1;
544: } else {
545: //attindex = lastStartTag_atts.getIndex("attributename");
546: attindex = lookupAttribute(lastStartTag_atts,
547: "attributename");
548: columnDef_valueType = 2;
549:
550: if (attindex == -1) {
551: throw new SAXException(
552: "column definition has 'valuelocation' tag, attribute type, but no 'attributename' attribute");
553: }
554:
555: columnDef_valueAttribute = new String(
556: lastStartTag_atts.getValue(attindex));
557: }
558: }
559:
560: if (qName.equalsIgnoreCase("column")) {
561: //commit column entry
562: if (columnDef_tagName.equalsIgnoreCase("")) {
563: throw new SAXException(
564: "column Definition didnt include tag name ('<name>...</name>')");
565: }
566:
567: if (columnDef_tagType == 0) {
568: throw new SAXException(
569: "column Definition didnt include 'valueelement' ");
570: }
571:
572: if (columnDef_valueType == 0) {
573: throw new SAXException(
574: "column Definition didnt have a 'valuelocation'");
575: }
576:
577: //we're okay
578: ColumnDescription colDes;
579:
580: colDes = new ColumnDescription();
581: colDes.setColumnName(columnDef_columnName);
582:
583: if (colDes.columnName.compareTo("GEOMETRY") == 0) {
584: throw new ParseException(
585: "Cannot have a column named GEOMETRY!");
586: }
587:
588: if (columnDef_valueType == 2) //auto set for #1=body
589: {
590: colDes.setValueAttribute(columnDef_valueAttribute); //not the body
591: }
592:
593: colDes.setTagName(columnDef_tagName);
594:
595: if (columnDef_tagType == 3) //1=simple
596: {
597: colDes.setTagAttribute(columnDef_tagAttribute,
598: columnDef_tagValue);
599: }
600:
601: if (columnDef_tagType == 2) {
602: colDes.setTagAttribute(columnDef_tagAttribute);
603: }
604:
605: colDes.setType(columnDef_type);
606: columnDefinitions.add(colDes); //remember this
607: }
608: } catch (EndOfParseException e) {
609: throw e;
610: } catch (Exception e) {
611: throw new SAXException(e.getMessage());
612: }
613: }
614:
615: /**
616: *SAX handler for characters - just store and accumulate for later use
617: */
618: public void characters(char[] ch, int start, int length)
619: throws SAXException {
620: try {
621: String part;
622: part = new String(ch, start, length);
623: tagBody = tagBody + part;
624: } catch (Exception e) {
625: throw new SAXException(e.getMessage());
626: }
627: }
628: }
|