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.21 2002/07/05 12:04:35 nasso 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     text = recoverNewLineSequence(text);
386     tmpDocContent.append(text);
387   }//processTextOfTextWithNodesElement
388 
389   /** Restore new line as in the original document if needed */
390   private String recoverNewLineSequence(String text) {
391     String result = text;
392 
393     // check for new line
394     if(text.indexOf('\n') != -1) {
395       String newLineType = 
396         (String) doc.getFeatures().get(GateConstants.DOCUMENT_NEW_LINE_TYPE);
397 
398       if("LF".equalsIgnoreCase(newLineType)) {
399         newLineType = null;
400       }
401       
402       // exit with the same text if the change isn't necessary
403       if(newLineType == null) return result;
404       
405       String newLine = "\n";
406       if("CRLF".equalsIgnoreCase(newLineType)) {
407         newLine = "\r\n";
408       }
409       if("CR".equalsIgnoreCase(newLineType)) {
410         newLine = "\r";
411       }
412       if("LFCR".equalsIgnoreCase(newLineType)) {
413         newLine = "\n\r";
414       }
415 
416       StringBuffer buff = new StringBuffer(text);      
417       int index = text.lastIndexOf('\n');
418       while(index != -1) {
419         buff.replace(index, index+1, newLine);
420         index = text.lastIndexOf('\n', index-1);
421       } // while
422       result = buff.toString();
423     } // if
424     
425     return result;
426   } // recoverNewLineSequence(String text)
427   
428   /** This method deals with a Text belonging to Name element. */
429   private void processTextOfNameElement(String text) throws GateSaxException{
430     if (currentFeatureMap == null)
431       throw new GateSaxException("Gate xml format processing error:" +
432       " Found a Name element that is not enclosed into a Feature one while" +
433       " analyzing the annotation " +
434       currentAnnot +
435       "Please check the document with a text editor or something before" +
436       " trying again.");
437     else{
438       // In the entities case, characters() gets called separately for each
439       // entity so the text needs to be appended.
440       if (currentFeatureName == null)
441           currentFeatureName = text;
442       else
443         currentFeatureName = currentFeatureName + text;
444     }// End If
445   }//processTextOfNameElement();
446 
447   /** This method deals with a Text belonging to Value element. */
448   private void processTextOfValueElement(String text) throws GateSaxException{
449     if (currentFeatureMap == null)
450       throw new GateSaxException("Gate xml format processing error:" +
451       " Found a Value element that is not enclosed into a Feature one while" +
452       " analyzing the annotation " +
453       currentAnnot+
454       "Please check the document with a text editor or something before" +
455       " trying again.");
456     else{
457       // In the entities case, characters() gets called separately for each
458       // entity so the text needs to be appended.
459       if (currentFeatureValue == null)
460         currentFeatureValue = text;
461       else
462         currentFeatureValue = currentFeatureValue + text;
463     }// End If
464   }//processTextOfValueElement();
465 
466   /** Creates a feature key using this information:
467     * currentFeatureKeyClassName, currentFeatureKeyItemClassName,
468     * currentFeatureName. See createFeatObject() method for more details.
469     */
470   private Object createFeatKey(){
471     return createFeatObject(currentFeatureKeyClassName,
472                             currentFeatureKeyItemClassName,
473                             currentFeatureName);
474   }//createFeatKey()
475 
476   /** Creates a feature value using this information:
477     * currentFeatureValueClassName, currentFeatureValueItemClassName,
478     * currentFeatureValue. See createFeatObject() method for more details.
479     */
480   private Object createFeatValue(){
481     return createFeatObject(currentFeatureValueClassName,
482                             currentFeatureValueItemClassName,
483                             currentFeatureValue);
484   }//createFeatValue()
485 
486   /** This method tries to reconstruct an object given its class name and its
487    *  string representation. If the object is a Collection then the items
488    *  from its string representation must be separated by a ";". In that
489    *  case, the currentFeatureValueItemClassName is used to create items
490    *  belonging to this class.
491    *  @param aFeatClassName represents the name of the class of
492    *  the feat object being created. If it is null then the javaLang.String will
493    *  be used as default.
494    *  @param aFeatItemClassName is it used only if aFeatClassName is a
495    *  collection.If it is null then java.lang.String will be used as default;
496    *  @param aFeatStringRepresentation sais it all
497    *  @return an Object created from  aFeatClassName and its
498    *  aFeatStringRepresentation. If not possible, then aFeatStringRepresentation
499    *  is returned.
500    *  @throws GateRuntimeException If it can't create an item, that
501    *  does not comply with its class definition, to add to the
502    *  collection.
503    */
504   private Object createFeatObject( String aFeatClassName,
505                                    String aFeatItemClassName,
506                                    String aFeatStringRepresentation){
507     // If the string rep is null then the object will be null;
508     if (aFeatStringRepresentation == null) return null;
509     if (aFeatClassName == null) aFeatClassName = "java.lang.String";
510     if (aFeatItemClassName == null) aFeatItemClassName = "java.lang.String";
511     Class currentFeatClass = null;
512     try{
513       currentFeatClass = Gate.getClassLoader().loadClass(aFeatClassName);
514     }catch (ClassNotFoundException cnfex){
515       return aFeatStringRepresentation;
516     }// End try
517     if (java.util.Collection.class.isAssignableFrom(currentFeatClass)){
518       Class itemClass = null;
519       Collection featObject = null;
520       try{
521         featObject = (Collection) currentFeatClass.newInstance();
522         try{
523           itemClass = Gate.getClassLoader().loadClass(aFeatItemClassName);
524         }catch(ClassNotFoundException cnfex){
525           Out.prln("Warning: Item class "+ aFeatItemClassName + " not found."+
526           "Adding items as Strings to the feature called \"" + currentFeatureName
527           + "\" in the annotation " + currentAnnot);
528           itemClass = java.lang.String.class;
529         }// End try
530         // Let's detect if itemClass takes a constructor with a String as param
531         Class[] paramsArray = new Class[1];
532         paramsArray[0] = java.lang.String.class;
533         Constructor itemConstructor = null;
534         boolean addItemAsString = false;
535         try{
536          itemConstructor = itemClass.getConstructor(paramsArray);
537         }catch (NoSuchMethodException  nsme){
538           addItemAsString = true;
539         }catch (SecurityException se){
540           addItemAsString = true;
541         }// End try
542         StringTokenizer strTok = new StringTokenizer(
543                                                 aFeatStringRepresentation,";");
544         Object[] params = new Object[1];
545         Object itemObj = null;
546         while (strTok.hasMoreTokens()){
547           String itemStrRep = strTok.nextToken();
548           if (addItemAsString) featObject.add(itemStrRep);
549           else{
550             params[0] = itemStrRep;
551             try{
552               itemObj = itemConstructor.newInstance(params);
553             }catch (Exception e){
554               throw new GateRuntimeException("An item("+
555                itemStrRep +
556               ")  does not comply with its class" +
557               " definition("+aFeatItemClassName+").Happened while tried to"+
558               " add feature: " +
559               aFeatStringRepresentation + " to the annotation " + currentAnnot);
560             }// End try
561             featObject.add(itemObj);
562           }// End if
563         }// End while
564       }catch(InstantiationException instex ){
565         return aFeatStringRepresentation;
566       }catch (IllegalAccessException iae){
567         return aFeatStringRepresentation;
568       }// End try
569       return featObject;
570     }// End if
571     // If currentfeatClass is not a Collection,test to see if
572     // it has a constructor that takes a String as param
573     Class[] params = new Class[1];
574     params[0] = java.lang.String.class;
575     try{
576       Constructor featConstr = currentFeatClass.getConstructor(params);
577       Object[] featConstrParams = new Object[1];
578       featConstrParams[0] = aFeatStringRepresentation;
579       Object featObject = featConstr.newInstance(featConstrParams);
580       return featObject;
581     } catch(Exception e){
582       return aFeatStringRepresentation;
583     }// End try
584   }// createFeatObject()
585 
586   /**
587     * This method is called when the SAX parser encounts a comment
588     * It works only if the XmlDocumentHandler implements a
589     * com.sun.parser.LexicalEventListener
590     */
591   public void comment(String text) throws SAXException {
592   }//comment
593 
594   /**
595     * This method is called when the SAX parser encounts a start of a CDATA
596     * section
597     * It works only if the XmlDocumentHandler implements a
598     * com.sun.parser.LexicalEventListener
599     */
600   public void startCDATA()throws SAXException {
601   }//startCDATA
602 
603   /**
604     * This method is called when the SAX parser encounts the end of a CDATA
605     * section.
606     * It works only if the XmlDocumentHandler implements a
607     * com.sun.parser.LexicalEventListener
608     */
609   public void endCDATA() throws SAXException {
610   }//endCDATA
611 
612   /**
613     * This method is called when the SAX parser encounts a parsed Entity
614     * It works only if the XmlDocumentHandler implements a
615     * com.sun.parser.LexicalEventListener
616     */
617   public void startParsedEntity(String name) throws SAXException {
618   }//startParsedEntity
619 
620   /**
621     * This method is called when the SAX parser encounts a parsed entity and
622     * informs the application if that entity was parsed or not
623     * It's working only if the CustomDocumentHandler implements a
624     *  com.sun.parser.LexicalEventListener
625     */
626   public void endParsedEntity(String name, boolean included)throws SAXException{
627   }//endParsedEntity
628 
629   //StatusReporter Implementation
630 
631   /**
632     * This methos is called when a listener is registered with this class
633     */
634   public void addStatusListener(StatusListener listener){
635     myStatusListeners.add(listener);
636   }//addStatusListener
637   /**
638     * This methos is called when a listener is removed
639     */
640   public void removeStatusListener(StatusListener listener){
641     myStatusListeners.remove(listener);
642   }//removeStatusListener
643   /**
644     * This methos is called whenever we need to inform the listener about an
645     * event.
646   */
647   protected void fireStatusChangedEvent(String text){
648     Iterator listenersIter = myStatusListeners.iterator();
649     while(listenersIter.hasNext())
650       ((StatusListener)listenersIter.next()).statusChanged(text);
651   }//fireStatusChangedEvent
652 
653   // XmlDocumentHandler member data
654 
655   /** This constant indicates when to fire the status listener.
656     * This listener will add an overhead and we don't want a big overhead.
657     * It will be callled from ELEMENTS_RATE to ELEMENTS_RATE
658     */
659   final static  int ELEMENTS_RATE = 128;
660 
661   /** This object indicates what to do when the parser encounts an error */
662   private SimpleErrorHandler _seh = new SimpleErrorHandler();
663 
664   /** The content of the XML document, without any tag */
665   private StringBuffer tmpDocContent = new StringBuffer("");
666 
667   /** A gate document */
668   private gate.Document doc = null;
669 
670   /** Listeners for status report */
671   protected List myStatusListeners = new LinkedList();
672 
673   /** This reports the the number of elements that have beed processed so far*/
674   private int elements = 0;
675 
676   /** We need a colection to retain all the CustomObjects that will be
677     * transformed into annotation over the gate document...
678     * At the end of every annotation set read the objects in the colector are
679     * transformed into annotations...
680     */
681   private List colector = null;
682   /** Maps nodes Ids to their offset in the document text. Those offsets will
683     * be used when creating annotations
684     */
685   private Map id2Offset = new TreeMap();
686   /** Holds the current element read.*/
687   private Stack currentElementStack = new Stack();
688   /** This inner objects maps an annotation object. When an annotation from the
689     * xml document was read this structure is filled out
690     */
691   private AnnotationObject currentAnnot = null;
692   /** A map holding current annotation's features*/
693   private FeatureMap  currentFeatureMap = null;
694   /** A key of the current feature*/
695   private String currentFeatureName = null;
696   /** The value of the current feature*/
697   private String currentFeatureValue = null;
698   /** The class name of the key in the current feature*/
699   private String currentFeatureKeyClassName = null;
700   /** If the key is a collection then we need to know the class name of the
701     * items present in this collection. The next field holds just that.
702     */
703   private String currentFeatureKeyItemClassName = null;
704   /** The class name for the value in the current feature*/
705   private String currentFeatureValueClassName = null;
706   /** If the value is a collection then we need to know the class name of the
707     * items present in this collection. The next field holds just that.
708     */
709   private String currentFeatureValueItemClassName = null;
710   /** the current annotation set that is being created and filled with
711     * annotations
712     */
713   private AnnotationSet currentAnnotationSet = null;
714 
715   /** An inner class modeling the information contained by an annotation.*/
716   class  AnnotationObject {
717     /** Constructor */
718     public AnnotationObject(){}//AnnotationObject
719     /** Accesor for the annotation type modeled here as ElemName */
720     public String getElemName(){
721       return elemName;
722     }//getElemName
723     /** Accesor for the feature map*/
724     public FeatureMap getFM(){
725       return fm;
726     }// getFM()
727     /** Accesor for the start ofset*/
728     public Long getStart(){
729       return start;
730     }// getStart()
731     /** Accesor for the end offset*/
732     public Long getEnd(){
733       return end;
734     }// getEnd()
735     /** Mutator for the annotation type */
736     public void setElemName(String anElemName){
737       elemName = anElemName;
738     }// setElemName();
739     /** Mutator for the feature map*/
740     public void setFM(FeatureMap aFm){
741       fm = aFm;
742     }// setFM();
743     /** Mutator for the start offset*/
744     public void setStart(Long aStart){
745       start = aStart;
746     }// setStart();
747     /** Mutator for the end offset*/
748     public void setEnd(Long anEnd){
749       end = anEnd;
750     }// setEnd();
751 
752     public String toString(){
753       return " [type=" + elemName +
754       " startNode=" + start+
755       " endNode=" + end+
756       " features="+ fm +"] ";
757     }
758     // Data fields
759     private String elemName = null;
760     private FeatureMap fm = null;
761     private Long start = null;
762     private Long end  = null;
763   } // AnnotationObject
764 }//GateFormatXmlDocumentHandler
765 
766