XmlDocumentHandler.java |
1 /* 2 * XmlDocumentHandler.java 3 * 4 * Copyright (c) 1998-2005, The University of Sheffield. 5 * 6 * This file is part of GATE (see http://gate.ac.uk/), and is free 7 * software, licenced under the GNU Library General Public License, 8 * Version 2, June 1991 (in the distribution as file licence.html, 9 * and also available at http://gate.ac.uk/gate/licence.html). 10 * 11 * Cristian URSU, 9 May 2000 12 * 13 * $Id: XmlDocumentHandler.java,v 1.49 2005/01/11 13:51:38 ian Exp $ 14 */ 15 16 package gate.xml; 17 18 import java.util.*; 19 20 import org.xml.sax.*; 21 22 import gate.*; 23 import gate.corpora.DocumentContentImpl; 24 import gate.corpora.RepositioningInfo; 25 import gate.event.StatusListener; 26 import gate.util.*; 27 28 29 /** 30 * Implements the behaviour of the XML reader 31 * Methods of an object of this class are called by the SAX parser when 32 * events will appear. 33 * The idea is to parse the XML document and construct Gate annotations 34 * objects. 35 * This class also will replace the content of the Gate document with a 36 * new one containing only text from the XML document. 37 */ 38 public class XmlDocumentHandler extends XmlPositionCorrectionHandler { 39 /** Debug flag */ 40 private static final boolean DEBUG = false; 41 42 /** Keep the refference to this structure */ 43 private RepositioningInfo reposInfo = null; 44 45 /** Keep the refference to this structure */ 46 private RepositioningInfo ampCodingInfo = null; 47 48 49 /** This is used to capture all data within two tags before calling the actual characters method */ 50 private StringBuffer contentBuffer = new StringBuffer(""); 51 52 /** This is a variable that shows if characters have been read */ 53 private boolean readCharacterStatus = false; 54 55 56 /** Set repositioning information structure refference. If you set this 57 * refference to <B>null</B> information wouldn't be collected. 58 */ 59 public void setRepositioningInfo(RepositioningInfo info) { 60 reposInfo = info; 61 } // setRepositioningInfo 62 63 /** Return current RepositioningInfo object */ 64 public RepositioningInfo getRepositioningInfo() { 65 return reposInfo; 66 } // getRepositioningInfo 67 68 /** Set repositioning information structure refference for ampersand coding. 69 * If you set this refference to <B>null</B> information wouldn't be used. 70 */ 71 public void setAmpCodingInfo(RepositioningInfo info) { 72 ampCodingInfo = info; 73 } // setRepositioningInfo 74 75 /** Return current RepositioningInfo object for ampersand coding. */ 76 public RepositioningInfo getAmpCodingInfo() { 77 return ampCodingInfo; 78 } // getRepositioningInfo 79 80 81 /** 82 * Constructs a XmlDocumentHandler object. The annotationSet set will be the 83 * default one taken from the gate document. 84 * @param aDocument the Gate document that will be processed. 85 * @param aMarkupElementsMap this map contains the elements name that we 86 * want to create. 87 * @param anElement2StringMap this map contains the strings that will be 88 * added to the text contained by the key element. 89 */ 90 public XmlDocumentHandler(gate.Document aDocument, Map aMarkupElementsMap, 91 Map anElement2StringMap){ 92 this(aDocument,aMarkupElementsMap,anElement2StringMap,null); 93 } // XmlDocumentHandler 94 95 /** 96 * Constructs a XmlDocumentHandler object. 97 * @param aDocument the Gate document that will be processed. 98 * @param aMarkupElementsMap this map contains the elements name that we 99 * want to create. 100 * @param anElement2StringMap this map contains the strings that will be 101 * added to the text contained by the key element. 102 * @param anAnnotationSet is the annotation set that will be filled when the 103 * document was processed 104 */ 105 public XmlDocumentHandler(gate.Document aDocument, 106 Map aMarkupElementsMap, 107 Map anElement2StringMap, 108 gate.AnnotationSet anAnnotationSet){ 109 // init parent 110 super(); 111 // init stack 112 stack = new java.util.Stack(); 113 114 // this string contains the plain text (the text without markup) 115 tmpDocContent = new StringBuffer(aDocument.getContent().size().intValue()); 116 117 // colector is used later to transform all custom objects into annotation 118 // objects 119 colector = new LinkedList(); 120 121 // the Gate document 122 doc = aDocument; 123 124 // this map contains the elements name that we want to create 125 // if it's null all the elements from the XML documents will be transformed 126 // into Gate annotation objects 127 markupElementsMap = aMarkupElementsMap; 128 129 // this map contains the string that we want to insert iside the document 130 // content, when a certain element is found 131 // if the map is null then no string is added 132 element2StringMap = anElement2StringMap; 133 134 basicAS = anAnnotationSet; 135 customObjectsId = 0; 136 }// XmlDocumentHandler()/ 137 138 /** 139 * This method is called when the SAX parser encounts the beginning of the 140 * XML document. 141 */ 142 public void startDocument() throws org.xml.sax.SAXException { 143 // init of variables in the parent 144 super.startDocument(); 145 } 146 147 /** 148 * This method is called when the SAX parser encounts the end of the 149 * XML document. 150 * Here we set the content of the gate Document to be the one generated 151 * inside this class (tmpDocContent). 152 * After that we use the colector to generate all the annotation reffering 153 * this new gate document. 154 */ 155 public void endDocument() throws org.xml.sax.SAXException { 156 157 // replace the document content with the one without markups 158 doc.setContent(new DocumentContentImpl(tmpDocContent.toString())); 159 160 // fire the status listener 161 fireStatusChangedEvent("Total elements: " + elements); 162 163 // If basicAs is null then get the default AnnotationSet, 164 // based on the gate document. 165 if (basicAS == null) 166 basicAS=doc.getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME); 167 168 // sort colector ascending on its id 169 Collections.sort(colector); 170 Set testIdsSet = new HashSet(); 171 // create all the annotations (on this new document) from the collector 172 while (!colector.isEmpty()){ 173 CustomObject obj = (CustomObject) colector.getFirst(); 174 // Test to see if there are two annotation objects with the same id. 175 if (testIdsSet.contains(obj.getId())){ 176 throw new GateSaxException("Found two annotations with the same Id("+ 177 obj.getId()+ 178 ").The document is inconsistent."); 179 }else{ 180 testIdsSet.add(obj.getId()); 181 }// End iff 182 // create a new annotation and add it to the annotation set 183 try{ 184 // the annotation type will be conforming with markupElementsMap 185 //add the annotation to the Annotation Set 186 if (markupElementsMap == null) 187 basicAS.add( obj.getId(), 188 obj.getStart(), 189 obj.getEnd(), 190 obj.getElemName(), 191 obj.getFM ()); 192 else { 193 // get the type of the annotation from Map 194 String annotationType = (String) 195 markupElementsMap.get(obj.getElemName()); 196 if (annotationType != null) 197 basicAS.add( obj.getId(), 198 obj.getStart(), 199 obj.getEnd(), 200 annotationType, 201 obj.getFM()); 202 }// End if 203 }catch (gate.util.InvalidOffsetException e){ 204 Err.prln("InvalidOffsetException for annot :" + obj.getElemName() + 205 " with Id =" + obj.getId() + ". Discarded..."); 206 }// End try 207 colector.remove(obj); 208 }// End while 209 }// endDocument(); 210 211 /** 212 * This method is called when the SAX parser encounts the beginning of an 213 * XML element. 214 */ 215 public void startElement (String uri, String qName, String elemName, 216 Attributes atts) throws SAXException { 217 218 // call characterActions 219 if(readCharacterStatus) { 220 readCharacterStatus = false; 221 charactersAction(new String(contentBuffer).toCharArray(),0,contentBuffer.length()); 222 } 223 224 // Inform the progress listener to fire only if no of elements processed 225 // so far is a multiple of ELEMENTS_RATE 226 if ((++elements % ELEMENTS_RATE) == 0) 227 fireStatusChangedEvent("Processed elements : " + elements); 228 229 Integer customObjectId = null; 230 // Construct a SimpleFeatureMapImpl from the list of attributes 231 FeatureMap fm = Factory.newFeatureMap(); 232 //Get the name and the value of the attributes and add them to a FeaturesMAP 233 for (int i = 0; i < atts.getLength(); i++) { 234 String attName = atts.getLocalName(i); 235 String attValue = atts.getValue(i); 236 String attUri = atts.getURI(i); 237 if (attUri != null && Gate.URI.equals(attUri)){ 238 if ("gateId".equals(attName)){ 239 customObjectId = new Integer(attValue); 240 }// End if 241 if ("annotMaxId".equals(attName)){ 242 customObjectsId = new Integer(attValue).intValue(); 243 }// End if 244 if ("matches".equals(attName)){ 245 StringTokenizer strTokenizer = new StringTokenizer(attValue,";"); 246 List list = new ArrayList(); 247 // Take all tokens,create Integers and add them to the list 248 while (strTokenizer.hasMoreTokens()){ 249 String token = strTokenizer.nextToken(); 250 list.add(new Integer(token)); 251 }// End while 252 fm.put(attName,list); 253 }// End if 254 }else{ 255 fm.put(atts.getQName(i), attValue); 256 }// End if 257 }// End for 258 259 // create the START index of the annotation 260 Long startIndex = new Long(tmpDocContent.length()); 261 262 // initialy the Start index is equal with End index 263 CustomObject obj = new CustomObject(customObjectId,elemName,fm, 264 startIndex, startIndex); 265 266 // put this object into the stack 267 stack.push(obj); 268 }// startElement(); 269 270 /** 271 * This method is called when the SAX parser encounts the end of an 272 * XML element. 273 * Here we extract 274 */ 275 public void endElement (String uri, String qName, String elemName ) 276 throws SAXException{ 277 // call characterActions 278 if(readCharacterStatus) { 279 readCharacterStatus = false; 280 charactersAction(new String(contentBuffer).toCharArray(),0,contentBuffer.length()); 281 } 282 283 // obj is for internal use 284 CustomObject obj = null; 285 286 // if the stack is not empty, we extract the custom object and delete it 287 if (!stack.isEmpty ()){ 288 obj = (CustomObject) stack.pop(); 289 }// End if 290 291 // Before adding it to the colector, we need to check if is an 292 // emptyAndSpan one. See CustomObject's isEmptyAndSpan field. 293 if (obj.getStart().equals(obj.getEnd())){ 294 // The element had an end tag and its start was equal to its end. Hence 295 // it is anEmptyAndSpan one. 296 obj.getFM().put("isEmptyAndSpan","true"); 297 }// End iff 298 299 // Put the object into colector 300 // Later, when the document ends we will use colector to create all the 301 // annotations 302 colector.add(obj); 303 304 // if element is found on Element2String map, then add the string to the 305 // end of the document content 306 if (element2StringMap != null){ 307 String stringFromMap = null; 308 309 // test to see if element is inside the map 310 // if it is then get the string value and add it to the document content 311 stringFromMap = (String) element2StringMap.get(elemName); 312 if (stringFromMap != null) 313 tmpDocContent.append(stringFromMap); 314 }// End if 315 }// endElement(); 316 317 /** 318 * This method is called when the SAX parser encounts text in the XML doc. 319 * Here we calculate the end indices for all the elements present inside the 320 * stack and update with the new values. For entities, this method is called 321 * separatley regardless of the text sourinding the entity. 322 */ 323 public void characters(char [] text,int start,int length) throws SAXException { 324 if(!readCharacterStatus) { 325 contentBuffer = new StringBuffer(new String(text,start,length)); 326 } else { 327 contentBuffer.append(new String(text,start,length)); 328 } 329 readCharacterStatus = true; 330 } 331 332 /** 333 * This method is called when all characters between specific tags have been read completely 334 */ 335 public void charactersAction( char[] text,int start,int length) throws SAXException{ 336 // correction of real offset. Didn't affect on other data. 337 super.characters(text, start, length); 338 // create a string object based on the reported text 339 String content = new String(text, start, length); 340 StringBuffer contentBuffer = new StringBuffer(""); 341 int tmpDocContentSize = tmpDocContent.length(); 342 boolean incrementStartIndex = false; 343 boolean addExtraSpace = true; 344 if ( Gate.getUserConfig().get( 345 GateConstants.DOCUMENT_ADD_SPACE_ON_UNPACK_FEATURE_NAME)!= null) 346 addExtraSpace = 347 Gate.getUserConfig().getBoolean( 348 GateConstants.DOCUMENT_ADD_SPACE_ON_UNPACK_FEATURE_NAME 349 ).booleanValue(); 350 // If the first char of the text just read "text[0]" is NOT whitespace AND 351 // the last char of the tmpDocContent[SIZE-1] is NOT whitespace then 352 // concatenation "tmpDocContent + content" will result into a new different 353 // word... and we want to avoid that, because the tokenizer, gazetter and 354 // Jape work on the raw text and concatenating tokens might be not good. 355 if ( tmpDocContentSize != 0 && 356 content.length() != 0 && 357 !Character.isWhitespace(content.charAt(0)) && 358 !Character.isWhitespace(tmpDocContent.charAt(tmpDocContentSize - 1))){ 359 360 // If we are here it means that a concatenation between the last 361 // token in the tmpDocContent and the content(which doesn't start 362 // with a white space) will be performed. In order to prevent this, 363 // we will add a " " space char in order to assure that the 2 tokens 364 // stay apart. Howerver we will except from this rule the most known 365 // internal entities like &, <, >, etc 366 if ( 367 ( 368 // Testing the length against 1 makes it more likely that 369 // an internal entity was called. characters() gets called for 370 // each entity separately. 371 (content.length() == 1) 372 && 373 (content.charAt(0) == '&' || 374 content.charAt(0) == '<' || 375 content.charAt(0) == '>' || 376 content.charAt(0) == '"' || 377 content.charAt(0) == '\'' 378 ) 379 ) || 380 (tmpDocContent.charAt(tmpDocContentSize - 1) == '&' || 381 tmpDocContent.charAt(tmpDocContentSize - 1) == '<' || 382 tmpDocContent.charAt(tmpDocContentSize - 1) == '>' || 383 tmpDocContent.charAt(tmpDocContentSize - 1) == '"' || 384 tmpDocContent.charAt(tmpDocContentSize - 1) == '\'' 385 )){// do nothing. The content will be appended 386 }else if (!addExtraSpace) { 387 }else 388 { 389 // In all other cases append " " 390 contentBuffer.append(" "); 391 incrementStartIndex = true; 392 }// End if 393 }// End if 394 395 // put the repositioning information 396 if(reposInfo != null) { 397 if(! (start == 0 && length == 1 && text.length <= 2)) { 398 // normal piece of text 399 reposInfo.addPositionInfo(getRealOffset(), content.length(), 400 tmpDocContent.length()+contentBuffer.length(), 401 content.length()); 402 if(DEBUG) { 403 Out.println("Info: "+getRealOffset()+", "+content.length()); 404 Out.println("Start: "+start+" len"+length); 405 } // DEBUG 406 } 407 else { 408 // unicode char or &xxx; coding 409 // Reported from the parser offset is 0 410 // The real offset should be found in the ampCodingInfo structure. 411 412 long lastPosition = 0; 413 RepositioningInfo.PositionInfo pi; 414 415 if(reposInfo.size() > 0) { 416 pi = 417 (RepositioningInfo.PositionInfo) reposInfo.get(reposInfo.size()-1); 418 lastPosition = pi.getOriginalPosition(); 419 } // if 420 421 for(int i = 0; i < ampCodingInfo.size(); ++i) { 422 pi = (RepositioningInfo.PositionInfo) ampCodingInfo.get(i); 423 if(pi.getOriginalPosition() > lastPosition) { 424 // found 425 reposInfo.addPositionInfo(pi.getOriginalPosition(), 426 pi.getOriginalLength(), 427 tmpDocContent.length()+contentBuffer.length(), 428 content.length()); 429 break; 430 } // if 431 } // for 432 } // if 433 } // if 434 435 // update the document content 436 contentBuffer.append(content); 437 // calculate the End index for all the elements of the stack 438 // the expression is : End index = Current doc length + text length 439 Long end = new Long(tmpDocContent.length() + contentBuffer.length()); 440 441 CustomObject obj = null; 442 // Iterate through stack to modify the End index of the existing elements 443 444 java.util.Iterator anIterator = stack.iterator(); 445 while (anIterator.hasNext ()){ 446 // get the object and move to the next one 447 obj = (CustomObject) anIterator.next (); 448 if (incrementStartIndex && obj.getStart().equals(obj.getEnd())){ 449 obj.setStart(new Long(obj.getStart().longValue() + 1)); 450 }// End if 451 // sets its End index 452 obj.setEnd(end); 453 }// End while 454 455 tmpDocContent.append(contentBuffer.toString()); 456 }// characters(); 457 458 /** 459 * This method is called when the SAX parser encounts white spaces 460 */ 461 public void ignorableWhitespace(char ch[],int start,int length) throws 462 SAXException{ 463 464 // internal String object 465 String text = new String(ch, start, length); 466 // if the last character in tmpDocContent is \n and the read whitespace is 467 // \n then don't add it to tmpDocContent... 468 469 if (tmpDocContent.length () != 0) 470 if (tmpDocContent.charAt (tmpDocContent.length () - 1) != '\n' || 471 !text.equalsIgnoreCase("\n") 472 ) 473 tmpDocContent.append(text); 474 } 475 476 /** 477 * Error method.We deal with this exception inside SimpleErrorHandler class 478 */ 479 public void error(SAXParseException ex) throws SAXException { 480 // deal with a SAXParseException 481 // see SimpleErrorhandler class 482 _seh.error(ex); 483 } 484 485 /** 486 * FatalError method. 487 */ 488 public void fatalError(SAXParseException ex) throws SAXException { 489 // deal with a SAXParseException 490 // see SimpleErrorhandler class 491 _seh.fatalError(ex); 492 } 493 494 /** 495 * Warning method comment. 496 */ 497 public void warning(SAXParseException ex) throws SAXException { 498 // deal with a SAXParseException 499 // see SimpleErrorhandler class 500 _seh.warning(ex); 501 } 502 503 /** 504 * This method is called when the SAX parser encounts a comment 505 * It works only if the XmlDocumentHandler implements a 506 * com.sun.parser.LexicalEventListener 507 */ 508 public void comment(String text) throws SAXException { 509 // create a FeatureMap and then add the comment to the annotation set. 510 /* 511 gate.util.SimpleFeatureMapImpl fm = new gate.util.SimpleFeatureMapImpl(); 512 fm.put ("text_comment",text); 513 Long node = new Long (tmpDocContent.length()); 514 CustomObject anObject = new CustomObject("Comment",fm,node,node); 515 colector.add(anObject); 516 */ 517 } 518 519 /** 520 * This method is called when the SAX parser encounts a start of a CDATA 521 * section 522 * It works only if the XmlDocumentHandler implements a 523 * com.sun.parser.LexicalEventListener 524 */ 525 public void startCDATA()throws SAXException { 526 } 527 528 /** 529 * This method is called when the SAX parser encounts the end of a CDATA 530 * section. 531 * It works only if the XmlDocumentHandler implements a 532 * com.sun.parser.LexicalEventListener 533 */ 534 public void endCDATA() throws SAXException { 535 } 536 537 /** 538 * This method is called when the SAX parser encounts a parsed Entity 539 * It works only if the XmlDocumentHandler implements a 540 * com.sun.parser.LexicalEventListener 541 */ 542 public void startParsedEntity(String name) throws SAXException { 543 } 544 545 /** 546 * This method is called when the SAX parser encounts a parsed entity and 547 * informs the application if that entity was parsed or not 548 * It's working only if the CustomDocumentHandler implements a 549 * com.sun.parser.LexicalEventListener 550 */ 551 public void endParsedEntity(String name, boolean included)throws SAXException{ 552 } 553 554 //StatusReporter Implementation 555 556 /** 557 * This methos is called when a listener is registered with this class 558 */ 559 public void addStatusListener(StatusListener listener){ 560 myStatusListeners.add(listener); 561 } 562 /** 563 * This methos is called when a listener is removed 564 */ 565 public void removeStatusListener(StatusListener listener){ 566 myStatusListeners.remove(listener); 567 } 568 /** 569 * This methos is called whenever we need to inform the listener about an 570 * event. 571 */ 572 protected void fireStatusChangedEvent(String text){ 573 Iterator listenersIter = myStatusListeners.iterator(); 574 while(listenersIter.hasNext()) 575 ((StatusListener)listenersIter.next()).statusChanged(text); 576 } 577 578 /** This method is a workaround of the java 4 non namespace supporting parser 579 * It receives a qualified name and returns its local name. 580 * For eg. if it receives gate:gateId it will return gateId 581 */ 582 private String getMyLocalName(String aQName){ 583 if (aQName == null) return ""; 584 StringTokenizer strToken = new StringTokenizer(aQName,":"); 585 if (strToken.countTokens()<= 1) return aQName; 586 // The nr of tokens is >= than 2 587 // Skip the first token which is the QName 588 strToken.nextToken(); 589 return strToken.nextToken(); 590 }//getMyLocalName() 591 592 /** Also a workaround for URI identifier. If the QName is gate it will return 593 * GATE's. Otherwhise it will return the empty string 594 */ 595 private String getMyURI(String aQName){ 596 if (aQName == null) return ""; 597 StringTokenizer strToken = new StringTokenizer(aQName,":"); 598 if (strToken.countTokens()<= 1) return ""; 599 // If first token is "gate" then return GATE's URI 600 if ("gate".equalsIgnoreCase(strToken.nextToken())) 601 return Gate.URI; 602 return ""; 603 }// getMyURI() 604 605 // XmlDocumentHandler member data 606 607 // this constant indicates when to fire the status listener 608 // this listener will add an overhead and we don't want a big overhead 609 // this listener will be callled from ELEMENTS_RATE to ELEMENTS_RATE 610 final static int ELEMENTS_RATE = 128; 611 612 // this map contains the elements name that we want to create 613 // if it's null all the elements from the XML documents will be transformed 614 // into Gate annotation objects otherwise only the elements it contains will 615 // be transformed 616 private Map markupElementsMap = null; 617 618 // this map contains the string that we want to insert iside the document 619 // content, when a certain element is found 620 // if the map is null then no string is added 621 private Map element2StringMap = null; 622 623 /**This object inducates what to do when the parser encounts an error*/ 624 private SimpleErrorHandler _seh = new SimpleErrorHandler(); 625 626 /**The content of the XML document, without any tag for internal use*/ 627 private StringBuffer tmpDocContent = null; 628 629 /**A stack used to remember elements and to keep the order */ 630 private java.util.Stack stack = null; 631 632 /**A gate document */ 633 private gate.Document doc = null; 634 635 /**An annotation set used for creating annotation reffering the doc */ 636 private gate.AnnotationSet basicAS = null; 637 638 /**Listeners for status report */ 639 protected List myStatusListeners = new LinkedList(); 640 641 /**This reports the the number of elements that have beed processed so far*/ 642 private int elements = 0; 643 644 /** We need a colection to retain all the CustomObjects that will be 645 * transformed into annotation over the gate document... 646 * the transformation will take place inside onDocumentEnd() method 647 */ 648 private LinkedList colector = null; 649 650 /** This is used to generate unique Ids for the CustomObjects read*/ 651 protected int customObjectsId = 0; 652 653 /** Accesor method for the customObjectsId field*/ 654 public int getCustomObjectsId(){ return customObjectsId;} 655 656 //////// INNER CLASS 657 /** 658 * The objects belonging to this class are used inside the stack. 659 * This class is for internal needs 660 */ 661 class CustomObject implements Comparable { 662 663 // constructor 664 public CustomObject(Integer anId,String anElemName, FeatureMap aFm, 665 Long aStart, Long anEnd) { 666 elemName = anElemName; 667 fm = aFm; 668 start = aStart; 669 end = anEnd; 670 if (anId == null){ 671 id = new Integer(customObjectsId ++); 672 }else{ 673 id = anId; 674 if (customObjectsId <= anId.intValue()) 675 customObjectsId = anId.intValue() + 1 ; 676 }// End if 677 }// End CustomObject() 678 679 // Methos implemented as required by Comparable interface 680 public int compareTo(Object o){ 681 CustomObject obj = (CustomObject) o; 682 return this.id.compareTo(obj.getId()); 683 }// compareTo(); 684 685 // accesor 686 public String getElemName() { 687 return elemName; 688 }// getElemName() 689 690 public FeatureMap getFM() { 691 return fm; 692 }// getFM() 693 694 public Long getStart() { 695 return start; 696 }// getStart() 697 698 public Long getEnd() { 699 return end; 700 }// getEnd() 701 702 public Integer getId(){ return id;} 703 704 // mutator 705 public void setElemName(String anElemName) { 706 elemName = anElemName; 707 }// getElemName() 708 709 public void setFM(FeatureMap aFm) { 710 fm = aFm; 711 }// setFM(); 712 713 public void setStart(Long aStart) { 714 start = aStart; 715 }// setStart(); 716 717 public void setEnd(Long anEnd) { 718 end = anEnd; 719 }// setEnd(); 720 721 // data fields 722 private String elemName = null; 723 private FeatureMap fm = null; 724 private Long start = null; 725 private Long end = null; 726 private Integer id = null; 727 728 } // End inner class CustomObject 729 730 } //XmlDocumentHandler 731 732 733 734