1   /*
2    *  GateFormatXmlDocumentHandler.java
3    *
4    *  Copyright (c) 1998-2001, 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,  22 Nov 2000
12   *
13   *  $Id: GateFormatXmlDocumentHandler.java,v 1.20 2001/11/15 17:10:45 cursu Exp $
14   */
15  
16  package gate.xml;
17  
18  import java.util.*;
19  import java.lang.reflect.*;
20  
21  import gate.corpora.*;
22  import gate.annotation.*;
23  import gate.util.*;
24  import gate.*;
25  import gate.event.*;
26  
27  
28  import org.xml.sax.*;
29  import org.xml.sax.helpers.*;
30  
31  
32  /**
33    * Implements the behaviour of the XML reader. This is the reader for
34    * Gate Xml documents saved with DocumentImplementation.toXml() method.
35    */
36  public class GateFormatXmlDocumentHandler extends DefaultHandler{
37    /** Debug flag */
38    private static final boolean DEBUG = false;
39  
40    /**
41      */
42    public GateFormatXmlDocumentHandler(gate.Document aDocument){
43      // This string contains the plain text (the text without markup)
44      tmpDocContent = new StringBuffer(aDocument.getContent().size().intValue());
45  
46      // Colector is used later to transform all custom objects into annotation
47      // objects
48      colector = new LinkedList();
49  
50      // The Gate document
51      doc = aDocument;
52      currentAnnotationSet = doc.getAnnotations();
53    }//GateFormatXmlDocumentHandler
54  
55    /**
56      * This method is called when the SAX parser encounts the beginning of the
57      * XML document.
58      */
59    public void startDocument() throws org.xml.sax.SAXException {
60    }// startDocument
61  
62    /**
63      * This method is called when the SAX parser encounts the end of the
64      * XML document.
65      * Here we set the content of the gate Document to be the one generated
66      * inside this class (tmpDocContent).
67      * After that we use the colector to generate all the annotation reffering
68      * this new gate document.
69      */
70    public void endDocument() throws org.xml.sax.SAXException {
71  
72      // replace the document content with the one without markups
73      doc.setContent(new DocumentContentImpl(tmpDocContent.toString()));
74      long docSize = doc.getContent().size().longValue();
75  
76      // fire the status listener
77      fireStatusChangedEvent("Total elements: " + elements);
78  
79    }// endDocument
80  
81    /**
82      * This method is called when the SAX parser encounts the beginning of an
83      * XML element.
84      */
85    public void startElement (String uri, String qName, String elemName,
86                                                               Attributes atts){
87  
88      // Inform the progress listener to fire only if no of elements processed
89      // so far is a multiple of ELEMENTS_RATE
90      if ((++elements % ELEMENTS_RATE) == 0 )
91          fireStatusChangedEvent("Processed elements : " + elements);
92  
93      // Set the curent element being processed
94      currentElementStack.add(elemName);
95  
96      if("AnnotationSet".equals(elemName))
97        processAnnotationSetElement(atts);
98  
99      if("Annotation".equals(elemName))
100       processAnnotationElement(atts);
101 
102     if("Feature".equals(elemName))
103       processFeatureElement(atts);
104 
105     if("Name".equals(elemName))
106       processNameElement(atts);
107 
108     if("Value".equals(elemName))
109       processValueElement(atts);
110 
111     if("Node".equals(elemName))
112       processNodeElement(atts);
113   }// startElement
114 
115   /**
116     * This method is called when the SAX parser encounts the end of an
117     * XML element.
118     */
119     public void endElement (String uri, String qName, String elemName )
120                                                            throws SAXException{
121 
122     currentElementStack.pop();
123     // Deal with Annotation
124     if ("Annotation".equals(elemName)){
125       if (currentFeatureMap == null)
126         currentFeatureMap = Factory.newFeatureMap();
127       currentAnnot.setFM(currentFeatureMap);
128       colector.add(currentAnnot);
129       // Reset current Annot and current featue map
130       currentAnnot = null;
131       currentFeatureMap = null;
132       return;
133     }// End if
134     // Deal with Value
135     if ("Value".equals(elemName) && "Feature".equals(
136                         (String)currentElementStack.peek())){
137       // If the Value tag was empty, then an empty string will be created.
138       if (currentFeatureValue == null) currentFeatureValue = "";
139     }// End if
140     // Deal with Feature
141     if ("Feature".equals(elemName)){
142       if(currentFeatureName == null){
143         // Cannot add the (key,value) pair to the map
144         // One of them is null something was wrong in the XML file.
145         throw new GateSaxException("A feature name was empty." +
146           "The annotation that cause it is " +
147           currentAnnot +
148           ".Please check the document with a text editor before trying again.");
149       }else {
150         if (currentFeatureMap == null){
151           // The XMl file was somehow altered and a start Feature wasn't found.
152           throw new GateSaxException("Document not consistent. A start"+
153           " feature element is missing. " +
154           "The annotation that cause it is " +
155           currentAnnot +
156           "Please check the document with a text editor before trying again.");
157         }// End if
158         // Create the appropiate feature name and values
159         // If those object cannot be created, their string representation will
160         // be used.
161         currentFeatureMap.put(createFeatKey(),createFeatValue());
162 //        currentFeatureMap.put(currentFeatureName,currentFeatureValue);
163         // Reset current key
164         currentFeatureKeyClassName = null;
165         currentFeatureKeyItemClassName = null;
166         currentFeatureName = null;
167         // Reset current value
168         currentFeatureValueClassName = null;
169         currentFeatureValueItemClassName = null;
170         currentFeatureValue = null;
171       }// End if
172       // Reset the Name & Value pair.
173       currentFeatureName = null;
174       currentFeatureValue = null;
175       return;
176     }//End if
177     // Deal GateDocumentFeatures
178     if ("GateDocumentFeatures".equals(elemName)){
179       if (currentFeatureMap == null)
180         currentFeatureMap = Factory.newFeatureMap();
181       doc.setFeatures(currentFeatureMap);
182       currentFeatureMap = null;
183       return;
184     }// End if
185 
186     // Deal with AnnotationSet
187     if ("AnnotationSet".equals(elemName)){
188       // Create and add annotations to the currentAnnotationSet
189       Iterator iterator = colector.iterator();
190       while (iterator.hasNext()){
191         AnnotationObject annot = (AnnotationObject) iterator.next();
192         // Clear the annot from the colector
193         iterator.remove();
194         // Create a new annotation and add it to the annotation set
195         try{
196           currentAnnotationSet.add(annot.getStart(),
197                                    annot.getEnd(),
198                                    annot.getElemName(),
199                                    annot.getFM());
200         }catch (gate.util.InvalidOffsetException e){
201           throw new GateSaxException(e);
202         }// End try
203       }// End while
204       // The colector is empty and ready for the next AnnotationSet
205       return;
206     }// End if
207 
208 
209   }//endElement
210 
211   /**
212     * This method is called when the SAX parser encounts text in the XML doc.
213     * Here we calculate the end indices for all the elements present inside the
214     * stack and update with the new values.
215     */
216   public void characters( char[] text,int start,int length) throws SAXException{
217     // Create a string object based on the reported text
218     String content = new String(text, start, length);
219     if ("TextWithNodes".equals((String)currentElementStack.peek())){
220       processTextOfTextWithNodesElement(content);
221       return;
222     }// End if
223     if ("Name".equals((String)currentElementStack.peek())){
224       processTextOfNameElement(content);
225       return;
226     }// End if
227     if ("Value".equals((String)currentElementStack.peek())){
228 //if (currentFeatureName != null && "string".equals(currentFeatureName) &&
229 //currentAnnot!= null && "Token".equals(currentAnnot.getElemName()) &&
230 //currentAnnot.getEnd().longValue() == 1063)
231 //System.out.println("Content=" + content + " start="+ start + " length=" + length);
232       processTextOfValueElement(content);
233       return;
234     }// End if
235   }//characters
236 
237   /**
238     * This method is called when the SAX parser encounts white spaces
239     */
240   public void ignorableWhitespace(char ch[],int start,int length) throws
241                                                                    SAXException{
242   }//ignorableWhitespace
243 
244   /**
245     * Error method.We deal with this exception inside SimpleErrorHandler class
246     */
247   public void error(SAXParseException ex) throws SAXException {
248     // deal with a SAXParseException
249     // see SimpleErrorhandler class
250     _seh.error(ex);
251   }//error
252 
253   /**
254     * FatalError method.
255     */
256   public void fatalError(SAXParseException ex) throws SAXException {
257     // deal with a SAXParseException
258     // see SimpleErrorhandler class
259     _seh.fatalError(ex);
260   }//fatalError
261 
262   /**
263     * Warning method comment.
264     */
265   public void warning(SAXParseException ex) throws SAXException {
266     // deal with a SAXParseException
267     // see SimpleErrorhandler class
268     _seh.warning(ex);
269   }//warning
270 
271   // Custom methods section
272 
273 
274   /** This method deals with a AnnotationSet element. */
275   private void processAnnotationSetElement(Attributes atts){
276     if (atts != null){
277       for (int i = 0; i < atts.getLength(); i++) {
278        // Extract name and value
279        String attName  = atts.getLocalName(i);
280        String attValue = atts.getValue(i);
281        if ("Name".equals(attName))
282           currentAnnotationSet = doc.getAnnotations(attValue);
283       }// End for
284     }// End if
285   }//processAnnotationSetElement
286 
287   /** This method deals with the start of a Name element*/
288   private void processNameElement(Attributes atts){
289     if (atts == null) return;
290     currentFeatureKeyClassName = atts.getValue("className");
291     currentFeatureKeyItemClassName = atts.getValue("itemClassName");
292   }// End processNameElement();
293 
294   /** This method deals with the start of a Value element*/
295   private void processValueElement(Attributes atts){
296     if (atts == null) return;
297     currentFeatureValueClassName = atts.getValue("className");
298     currentFeatureValueItemClassName = atts.getValue("itemClassName");
299   }// End processValueElement();
300 
301   /** This method deals with a Annotation element. */
302   private void processAnnotationElement(Attributes atts){
303     if (atts != null){
304       currentAnnot = new AnnotationObject();
305       for (int i = 0; i < atts.getLength(); i++) {
306        // Extract name and value
307        String attName  = atts.getLocalName(i);
308        String attValue = atts.getValue(i);
309 
310        if ("Type".equals(attName))
311          currentAnnot.setElemName(attValue);
312 
313        try{
314          if ("StartNode".equals(attName)){
315           Integer id = new Integer(attValue);
316           Long offset = (Long)id2Offset.get(id);
317           if (offset == null){
318             throw new GateRuntimeException("Couldn't found Node with id = " +
319             id +
320             ".It was specified in annot " +
321             currentAnnot+
322             " as a start node!" +
323             "Check the document with a text editor or something"+
324             " before trying again.");
325 
326           }else
327             currentAnnot.setStart(offset);
328          }// Endif
329          if ("EndNode".equals(attName)){
330           Integer id = new Integer(attValue);
331           Long offset = (Long) id2Offset.get(id);
332           if (offset == null){
333             throw new GateRuntimeException("Couldn't found Node with id = " +
334             id+
335             ".It was specified in annot " +
336             currentAnnot+
337             " as a end node!" +
338             "Check the document with a text editor or something"+
339             " before trying again.");
340           }else
341             currentAnnot.setEnd(offset);
342          }// End if
343        } catch (NumberFormatException e){
344           throw new GateRuntimeException("Offsets problems.Couldn't create"+
345           " Integers from" + " id[" +
346           attValue + "]) in annot " +
347           currentAnnot+
348           "Check the document with a text editor or something,"+
349           " before trying again");
350        }// End try
351       }// End For
352     }// End if
353   }//processAnnotationElement
354 
355   /** This method deals with a Features element. */
356   private void processFeatureElement(Attributes atts){
357     // The first time feature is calle it will create a features map.
358     if (currentFeatureMap == null)
359       currentFeatureMap = Factory.newFeatureMap();
360   }//processFeatureElement
361 
362   /** This method deals with a Node element. */
363   private void processNodeElement(Attributes atts){
364     if (atts != null){
365       for (int i = 0; i < atts.getLength(); i++) {
366         // Extract name and value
367         String attName  = atts.getLocalName(i);
368         String attValue = atts.getValue(i);
369 //System.out.println("Node : " + attName + "=" +attValue);
370         if ("id".equals(attName)){
371           try{
372             Integer id = new Integer(attValue);
373             id2Offset.put(id,new Long(tmpDocContent.length()));
374           }catch(NumberFormatException e){
375             throw new GateRuntimeException("Coudn't create a node from " +
376                         attValue + " Expected an integer.");
377           }// End try
378         }// End if
379       }// End for
380     }// End if
381   }// processNodeElement();
382 
383   /** This method deals with a Text belonging to TextWithNodes element. */
384   private void processTextOfTextWithNodesElement(String text){
385     tmpDocContent.append(text);
386   }//processTextOfTextWithNodesElement
387 
388   /** This method deals with a Text belonging to Name element. */
389   private void processTextOfNameElement(String text) throws GateSaxException{
390     if (currentFeatureMap == null)
391       throw new GateSaxException("Gate xml format processing error:" +
392       " Found a Name element that is not enclosed into a Feature one while" +
393       " analyzing the annotation " +
394       currentAnnot +
395       "Please check the document with a text editor or something before" +
396       " trying again.");
397     else{
398       // In the entities case, characters() gets called separately for each
399       // entity so the text needs to be appended.
400       if (currentFeatureName == null)
401           currentFeatureName = text;
402       else
403         currentFeatureName = currentFeatureName + text;
404     }// End If
405   }//processTextOfNameElement();
406 
407   /** This method deals with a Text belonging to Value element. */
408   private void processTextOfValueElement(String text) throws GateSaxException{
409     if (currentFeatureMap == null)
410       throw new GateSaxException("Gate xml format processing error:" +
411       " Found a Value element that is not enclosed into a Feature one while" +
412       " analyzing the annotation " +
413       currentAnnot+
414       "Please check the document with a text editor or something before" +
415       " trying again.");
416     else{
417       // In the entities case, characters() gets called separately for each
418       // entity so the text needs to be appended.
419       if (currentFeatureValue == null)
420         currentFeatureValue = text;
421       else
422         currentFeatureValue = currentFeatureValue + text;
423     }// End If
424   }//processTextOfValueElement();
425 
426   /** Creates a feature key using this information:
427     * currentFeatureKeyClassName, currentFeatureKeyItemClassName,
428     * currentFeatureName. See createFeatObject() method for more details.
429     */
430   private Object createFeatKey(){
431     return createFeatObject(currentFeatureKeyClassName,
432                             currentFeatureKeyItemClassName,
433                             currentFeatureName);
434   }//createFeatKey()
435 
436   /** Creates a feature value using this information:
437     * currentFeatureValueClassName, currentFeatureValueItemClassName,
438     * currentFeatureValue. See createFeatObject() method for more details.
439     */
440   private Object createFeatValue(){
441     return createFeatObject(currentFeatureValueClassName,
442                             currentFeatureValueItemClassName,
443                             currentFeatureValue);
444   }//createFeatValue()
445 
446   /** This method tries to reconstruct an object given its class name and its
447    *  string representation. If the object is a Collection then the items
448    *  from its string representation must be separated by a ";". In that
449    *  case, the currentFeatureValueItemClassName is used to create items
450    *  belonging to this class.
451    *  @param aFeatClassName represents the name of the class of
452    *  the feat object being created. If it is null then the javaLang.String will
453    *  be used as default.
454    *  @param aFeatItemClassName is it used only if aFeatClassName is a
455    *  collection.If it is null then java.lang.String will be used as default;
456    *  @param aFeatStringRepresentation sais it all
457    *  @return an Object created from  aFeatClassName and its
458    *  aFeatStringRepresentation. If not possible, then aFeatStringRepresentation
459    *  is returned.
460    *  @throws GateRuntimeException If it can't create an item, that
461    *  does not comply with its class definition, to add to the
462    *  collection.
463    */
464   private Object createFeatObject( String aFeatClassName,
465                                    String aFeatItemClassName,
466                                    String aFeatStringRepresentation){
467     // If the string rep is null then the object will be null;
468     if (aFeatStringRepresentation == null) return null;
469     if (aFeatClassName == null) aFeatClassName = "java.lang.String";
470     if (aFeatItemClassName == null) aFeatItemClassName = "java.lang.String";
471     Class currentFeatClass = null;
472     try{
473       currentFeatClass = Gate.getClassLoader().loadClass(aFeatClassName);
474     }catch (ClassNotFoundException cnfex){
475       return aFeatStringRepresentation;
476     }// End try
477     if (java.util.Collection.class.isAssignableFrom(currentFeatClass)){
478       Class itemClass = null;
479       Collection featObject = null;
480       try{
481         featObject = (Collection) currentFeatClass.newInstance();
482         try{
483           itemClass = Gate.getClassLoader().loadClass(aFeatItemClassName);
484         }catch(ClassNotFoundException cnfex){
485           Out.prln("Warning: Item class "+ aFeatItemClassName + " not found."+
486           "Adding items as Strings to the feature called \"" + currentFeatureName
487           + "\" in the annotation " + currentAnnot);
488           itemClass = java.lang.String.class;
489         }// End try
490         // Let's detect if itemClass takes a constructor with a String as param
491         Class[] paramsArray = new Class[1];
492         paramsArray[0] = java.lang.String.class;
493         Constructor itemConstructor = null;
494         boolean addItemAsString = false;
495         try{
496          itemConstructor = itemClass.getConstructor(paramsArray);
497         }catch (NoSuchMethodException  nsme){
498           addItemAsString = true;
499         }catch (SecurityException se){
500           addItemAsString = true;
501         }// End try
502         StringTokenizer strTok = new StringTokenizer(
503                                                 aFeatStringRepresentation,";");
504         Object[] params = new Object[1];
505         Object itemObj = null;
506         while (strTok.hasMoreTokens()){
507           String itemStrRep = strTok.nextToken();
508           if (addItemAsString) featObject.add(itemStrRep);
509           else{
510             params[0] = itemStrRep;
511             try{
512               itemObj = itemConstructor.newInstance(params);
513             }catch (Exception e){
514               throw new GateRuntimeException("An item("+
515                itemStrRep +
516               ")  does not comply with its class" +
517               " definition("+aFeatItemClassName+").Happened while tried to"+
518               " add feature: " +
519               aFeatStringRepresentation + " to the annotation " + currentAnnot);
520             }// End try
521             featObject.add(itemObj);
522           }// End if
523         }// End while
524       }catch(InstantiationException instex ){
525         return aFeatStringRepresentation;
526       }catch (IllegalAccessException iae){
527         return aFeatStringRepresentation;
528       }// End try
529       return featObject;
530     }// End if
531     // If currentfeatClass is not a Collection,test to see if
532     // it has a constructor that takes a String as param
533     Class[] params = new Class[1];
534     params[0] = java.lang.String.class;
535     try{
536       Constructor featConstr = currentFeatClass.getConstructor(params);
537       Object[] featConstrParams = new Object[1];
538       featConstrParams[0] = aFeatStringRepresentation;
539       Object featObject = featConstr.newInstance(featConstrParams);
540       return featObject;
541     } catch(Exception e){
542       return aFeatStringRepresentation;
543     }// End try
544   }// createFeatObject()
545 
546   /**
547     * This method is called when the SAX parser encounts a comment
548     * It works only if the XmlDocumentHandler implements a
549     * com.sun.parser.LexicalEventListener
550     */
551   public void comment(String text) throws SAXException {
552   }//comment
553 
554   /**
555     * This method is called when the SAX parser encounts a start of a CDATA
556     * section
557     * It works only if the XmlDocumentHandler implements a
558     * com.sun.parser.LexicalEventListener
559     */
560   public void startCDATA()throws SAXException {
561   }//startCDATA
562 
563   /**
564     * This method is called when the SAX parser encounts the end of a CDATA
565     * section.
566     * It works only if the XmlDocumentHandler implements a
567     * com.sun.parser.LexicalEventListener
568     */
569   public void endCDATA() throws SAXException {
570   }//endCDATA
571 
572   /**
573     * This method is called when the SAX parser encounts a parsed Entity
574     * It works only if the XmlDocumentHandler implements a
575     * com.sun.parser.LexicalEventListener
576     */
577   public void startParsedEntity(String name) throws SAXException {
578   }//startParsedEntity
579 
580   /**
581     * This method is called when the SAX parser encounts a parsed entity and
582     * informs the application if that entity was parsed or not
583     * It's working only if the CustomDocumentHandler implements a
584     *  com.sun.parser.LexicalEventListener
585     */
586   public void endParsedEntity(String name, boolean included)throws SAXException{
587   }//endParsedEntity
588 
589   //StatusReporter Implementation
590 
591   /**
592     * This methos is called when a listener is registered with this class
593     */
594   public void addStatusListener(StatusListener listener){
595     myStatusListeners.add(listener);
596   }//addStatusListener
597   /**
598     * This methos is called when a listener is removed
599     */
600   public void removeStatusListener(StatusListener listener){
601     myStatusListeners.remove(listener);
602   }//removeStatusListener
603   /**
604     * This methos is called whenever we need to inform the listener about an
605     * event.
606   */
607   protected void fireStatusChangedEvent(String text){
608     Iterator listenersIter = myStatusListeners.iterator();
609     while(listenersIter.hasNext())
610       ((StatusListener)listenersIter.next()).statusChanged(text);
611   }//fireStatusChangedEvent
612 
613   // XmlDocumentHandler member data
614 
615   /** This constant indicates when to fire the status listener.
616     * This listener will add an overhead and we don't want a big overhead.
617     * It will be callled from ELEMENTS_RATE to ELEMENTS_RATE
618     */
619   final static  int ELEMENTS_RATE = 128;
620 
621   /** This object indicates what to do when the parser encounts an error */
622   private SimpleErrorHandler _seh = new SimpleErrorHandler();
623 
624   /** The content of the XML document, without any tag */
625   private StringBuffer tmpDocContent = new StringBuffer("");
626 
627   /** A gate document */
628   private gate.Document doc = null;
629 
630   /** Listeners for status report */
631   protected List myStatusListeners = new LinkedList();
632 
633   /** This reports the the number of elements that have beed processed so far*/
634   private int elements = 0;
635 
636   /** We need a colection to retain all the CustomObjects that will be
637     * transformed into annotation over the gate document...
638     * At the end of every annotation set read the objects in the colector are
639     * transformed into annotations...
640     */
641   private List colector = null;
642   /** Maps nodes Ids to their offset in the document text. Those offsets will
643     * be used when creating annotations
644     */
645   private Map id2Offset = new TreeMap();
646   /** Holds the current element read.*/
647   private Stack currentElementStack = new Stack();
648   /** This inner objects maps an annotation object. When an annotation from the
649     * xml document was read this structure is filled out
650     */
651   private AnnotationObject currentAnnot = null;
652   /** A map holding current annotation's features*/
653   private FeatureMap  currentFeatureMap = null;
654   /** A key of the current feature*/
655   private String currentFeatureName = null;
656   /** The value of the current feature*/
657   private String currentFeatureValue = null;
658   /** The class name of the key in the current feature*/
659   private String currentFeatureKeyClassName = null;
660   /** If the key is a collection then we need to know the class name of the
661     * items present in this collection. The next field holds just that.
662     */
663   private String currentFeatureKeyItemClassName = null;
664   /** The class name for the value in the current feature*/
665   private String currentFeatureValueClassName = null;
666   /** If the value is a collection then we need to know the class name of the
667     * items present in this collection. The next field holds just that.
668     */
669   private String currentFeatureValueItemClassName = null;
670   /** the current annotation set that is being created and filled with
671     * annotations
672     */
673   private AnnotationSet currentAnnotationSet = null;
674 
675   /** An inner class modeling the information contained by an annotation.*/
676   class  AnnotationObject {
677     /** Constructor */
678     public AnnotationObject(){}//AnnotationObject
679     /** Accesor for the annotation type modeled here as ElemName */
680     public String getElemName(){
681       return elemName;
682     }//getElemName
683     /** Accesor for the feature map*/
684     public FeatureMap getFM(){
685       return fm;
686     }// getFM()
687     /** Accesor for the start ofset*/
688     public Long getStart(){
689       return start;
690     }// getStart()
691     /** Accesor for the end offset*/
692     public Long getEnd(){
693       return end;
694     }// getEnd()
695     /** Mutator for the annotation type */
696     public void setElemName(String anElemName){
697       elemName = anElemName;
698     }// setElemName();
699     /** Mutator for the feature map*/
700     public void setFM(FeatureMap aFm){
701       fm = aFm;
702     }// setFM();
703     /** Mutator for the start offset*/
704     public void setStart(Long aStart){
705       start = aStart;
706     }// setStart();
707     /** Mutator for the end offset*/
708     public void setEnd(Long anEnd){
709       end = anEnd;
710     }// setEnd();
711 
712     public String toString(){
713       return " [type=" + elemName +
714       " startNode=" + start+
715       " endNode=" + end+
716       " features="+ fm +"] ";
717     }
718     // Data fields
719     private String elemName = null;
720     private FeatureMap fm = null;
721     private Long start = null;
722     private Long end  = null;
723   } // AnnotationObject
724 }//GateFormatXmlDocumentHandler
725 
726