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