XmlDocumentHandler.java
001 /*
002  *  XmlDocumentHandler.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Cristian URSU,  9 May 2000
013  *
014  *  $Id: XmlDocumentHandler.java 17649 2014-03-13 15:04:17Z markagreenwood $
015  */
016 package gate.xml;
017 
018 import gate.AnnotationSet;
019 import gate.Factory;
020 import gate.FeatureMap;
021 import gate.Gate;
022 import gate.GateConstants;
023 import gate.corpora.DocumentContentImpl;
024 import gate.corpora.RepositioningInfo;
025 import gate.event.StatusListener;
026 import gate.util.Err;
027 import gate.util.GateSaxException;
028 import gate.util.OptionsMap;
029 import gate.util.Out;
030 
031 import java.util.ArrayList;
032 import java.util.Collections;
033 import java.util.HashSet;
034 import java.util.Iterator;
035 import java.util.LinkedList;
036 import java.util.List;
037 import java.util.Map;
038 import java.util.Set;
039 import java.util.Stack;
040 import java.util.StringTokenizer;
041 
042 import org.xml.sax.Attributes;
043 import org.xml.sax.SAXException;
044 import org.xml.sax.SAXParseException;
045 
046 /**
047  * Implements the behaviour of the XML reader
048  * Methods of an object of this class are called by the SAX parser when
049  * events will appear.
050  * The idea is to parse the XML document and construct Gate annotations
051  * objects.
052  * This class also will replace the content of the Gate document with a
053  * new one containing only text from the XML document.
054  */
055 public class XmlDocumentHandler extends XmlPositionCorrectionHandler {
056 
057   /** Debug flag */
058   private static final boolean DEBUG = false;
059   /** Keep the refference to this structure */
060   private RepositioningInfo reposInfo = null;
061   /** Keep the refference to this structure */
062   private RepositioningInfo ampCodingInfo = null;
063   /** This is used to capture all data within two tags before calling the actual characters method */
064   private StringBuffer contentBuffer = new StringBuffer("");
065   /** This is a variable that shows if characters have been read */
066   private boolean readCharacterStatus = false;
067 
068 
069   /** Flag to determine whether to deserialize namespace information into
070    *  annotation features within Original markups AS
071    */
072   private boolean deserializeNamespaceInfo = false;
073   /** Feature name to use for namespace uri in namespaced elements */
074   private String namespaceURIFeature = null;
075   /** Feature name to use for namespace prefix in namespaced elements */
076   private String namespacePrefixFeature = null;
077 
078   /** Set repositioning information structure refference. If you set this
079    *  refference to <B>null</B> information wouldn't be collected.
080    */
081   public void setRepositioningInfo(RepositioningInfo info) {
082     reposInfo = info;
083   // setRepositioningInfo
084 
085   /** Return current RepositioningInfo object */
086   public RepositioningInfo getRepositioningInfo() {
087     return reposInfo;
088   // getRepositioningInfo
089 
090   /** Set repositioning information structure refference for ampersand coding.
091    *  If you set this refference to <B>null</B> information wouldn't be used.
092    */
093   public void setAmpCodingInfo(RepositioningInfo info) {
094     ampCodingInfo = info;
095   // setRepositioningInfo
096 
097   /** Return current RepositioningInfo object for ampersand coding. */
098   public RepositioningInfo getAmpCodingInfo() {
099     return ampCodingInfo;
100   // getRepositioningInfo
101 
102 
103   /**
104    * Constructs a XmlDocumentHandler object. The annotationSet set will be the
105    * default one taken from the gate document.
106    @param aDocument the Gate document that will be processed.
107    @param aMarkupElementsMap this map contains the elements name that we
108    * want to create.
109    @param anElement2StringMap this map contains the strings that will be
110    * added to the text contained by the key element.
111    */
112   public XmlDocumentHandler(gate.Document aDocument, Map<String,String> aMarkupElementsMap,
113           Map<String,String> anElement2StringMap) {
114     this(aDocument, aMarkupElementsMap, anElement2StringMap, null);
115   // XmlDocumentHandler
116 
117   /**
118    * Constructs a XmlDocumentHandler object.
119    @param aDocument the Gate document that will be processed.
120    @param aMarkupElementsMap this map contains the elements name that we
121    * want to create.
122    @param anElement2StringMap this map contains the strings that will be
123    * added to the text contained by the key element.
124    @param anAnnotationSet is the annotation set that will be filled when the
125    * document was processed
126    */
127   public XmlDocumentHandler(gate.Document aDocument,
128           Map<String,String> aMarkupElementsMap,
129           Map<String,String> anElement2StringMap,
130           AnnotationSet anAnnotationSet) {
131     // init parent
132     super();
133     // init stack
134     stack = new Stack<CustomObject>();
135 
136     // this string contains the plain text (the text without markup)
137     tmpDocContent = new StringBuffer(aDocument.getContent().size().intValue());
138 
139     // colector is used later to transform all custom objects into annotation
140     // objects
141     colector = new LinkedList<CustomObject>();
142 
143     // the Gate document
144     doc = aDocument;
145 
146     // this map contains the elements name that we want to create
147     // if it's null all the elements from the XML documents will be transformed
148     // into Gate annotation objects
149     markupElementsMap = aMarkupElementsMap;
150 
151     // this map contains the string that we want to insert iside the document
152     // content, when a certain element is found
153     // if the map is null then no string is added
154     element2StringMap = anElement2StringMap;
155 
156     basicAS = anAnnotationSet;
157     customObjectsId = 0;
158   }// XmlDocumentHandler()/
159 
160 
161   /**
162    * This method is called when the SAX parser encounts the beginning of the
163    * XML document.
164    */
165   @Override
166   public void startDocument() throws org.xml.sax.SAXException {
167     // init of variables in the parent
168     super.startDocument();
169 
170     /** We will attempt to add namespace feature info to each namespaced element
171      *  only if three parameters are set in the global or local config file:
172      *  ADD_NAMESPACE_FEATURES: boolean flag
173      *  ELEMENT_NAMESPACE_URI: feature name to use to hold namespace uri
174      *  ELEMENT_NAMESPACE_PREFIX: feature name to use to hold namespace prefix
175      */
176     OptionsMap configData = Gate.getUserConfig();
177 
178     boolean addNSFeature = Boolean.parseBoolean((StringconfigData.get(GateConstants.ADD_NAMESPACE_FEATURES));
179     namespaceURIFeature = (StringconfigData.get(GateConstants.ELEMENT_NAMESPACE_URI);
180     namespacePrefixFeature = (StringconfigData.get(GateConstants.ELEMENT_NAMESPACE_PREFIX);
181 
182     deserializeNamespaceInfo = (addNSFeature && namespacePrefixFeature != null && !namespacePrefixFeature.isEmpty() && namespaceURIFeature != null && !namespaceURIFeature.isEmpty());
183 
184   }
185 
186   /**
187    * This method is called when the SAX parser encounts the end of the
188    * XML document.
189    * Here we set the content of the gate Document to be the one generated
190    * inside this class (tmpDocContent).
191    * After that we use the colector to generate all the annotation reffering
192    * this new gate document.
193    */
194   @Override
195   public void endDocument() throws org.xml.sax.SAXException {
196 
197     // replace the document content with the one without markups
198     doc.setContent(new DocumentContentImpl(tmpDocContent.toString()));
199 
200     // fire the status listener
201     fireStatusChangedEvent("Total elements: " + elements);
202 
203     // If basicAs is null then get the default AnnotationSet,
204     // based on the gate document.
205     if (basicAS == null) {
206       basicAS = doc.getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
207     }
208 
209     // sort colector ascending on its id
210     Collections.sort(colector);
211     Set<Integer> testIdsSet = new HashSet<Integer>();
212     // create all the annotations (on this new document) from the collector
213     while (!colector.isEmpty()) {
214       CustomObject obj = colector.getFirst();
215       // Test to see if there are two annotation objects with the same id.
216       if (testIdsSet.contains(obj.getId())) {
217         throw new GateSaxException("Found two annotations with the same Id(" +
218                 obj.getId() +
219                 ").The document is inconsistent.");
220       else {
221         testIdsSet.add(obj.getId());
222       }// End iff
223       // create a new annotation and add it to the annotation set
224       try {
225         // the annotation type will be conforming with markupElementsMap
226         //add the annotation to the Annotation Set
227         if (markupElementsMap == null) {
228           basicAS.add(obj.getId(),
229                   obj.getStart(),
230                   obj.getEnd(),
231                   obj.getElemName(),
232                   obj.getFM());
233         else {
234           // get the type of the annotation from Map
235           String annotationType = markupElementsMap.get(obj.getElemName());
236           if (annotationType != null) {
237             basicAS.add(obj.getId(),
238                     obj.getStart(),
239                     obj.getEnd(),
240                     annotationType,
241                     obj.getFM());
242           }
243         }// End if
244       catch (gate.util.InvalidOffsetException e) {
245         Err.prln("InvalidOffsetException for annot :" + obj.getElemName() +
246                 " with Id =" + obj.getId() ". Discarded...");
247       }// End try
248       colector.remove(obj);
249     }// End while
250   }// endDocument();
251 
252 
253   /**
254    * This method is called when the SAX parser encounts the beginning of an
255    * XML element.
256    */
257   /**
258    *
259    @param uri - namespace uri
260    @param localName - local, unprefixed element name
261    @param qName - fully qualified, prefixed element name
262    @param atts
263    @throws SAXException
264    */
265   @Override
266   public void startElement(String uri, String localName, String qName,
267           Attributes attsthrows SAXException {
268 
269     // call characterActions
270     if (readCharacterStatus) {
271       readCharacterStatus = false;
272       charactersAction(new String(contentBuffer).toCharArray()0, contentBuffer.length());
273     }
274 
275     // Inform the progress listener to fire only if no of elements processed
276     // so far is a multiple of ELEMENTS_RATE
277     if ((++elements % ELEMENTS_RATE== 0) {
278       fireStatusChangedEvent("Processed elements : " + elements);
279     }
280 
281     Integer customObjectId = null;
282     // Construct a SimpleFeatureMapImpl from the list of attributes
283     FeatureMap fm = Factory.newFeatureMap();
284 
285     /** Use localName rather than qName and add the namespace prefix and uri
286      *  as features if global flag is set
287      */
288     String elemName = qName;
289     boolean hasNSUri = (uri != null && !uri.isEmpty());
290     if (deserializeNamespaceInfo && hasNSUri) {
291       elemName = localName;
292       StringTokenizer strToken = new StringTokenizer(qName, ":");
293       if (strToken.countTokens() 1) {
294         String nsPrefix = strToken.nextToken();
295         fm.put(namespaceURIFeature, uri);
296         fm.put(namespacePrefixFeature, nsPrefix);
297       }
298     }
299 
300 
301     //Get the name and the value of the attributes and add them to a FeaturesMAP
302     for (int i = 0; i < atts.getLength(); i++) {
303       String attName = atts.getLocalName(i);
304       String attValue = atts.getValue(i);
305       String attUri = atts.getURI(i);
306       if (attUri != null && Gate.URI.equals(attUri)) {
307         if ("gateId".equals(attName)) {
308           customObjectId = new Integer(attValue);
309         }// End if
310         if ("annotMaxId".equals(attName)) {
311           customObjectsId = new Integer(attValue).intValue();
312         }// End if
313         if ("matches".equals(attName)) {
314           StringTokenizer strTokenizer = new StringTokenizer(attValue, ";");
315           List<Integer> list = new ArrayList<Integer>();
316           // Take all tokens,create Integers and add them to the list
317           while (strTokenizer.hasMoreTokens()) {
318             String token = strTokenizer.nextToken();
319             list.add(new Integer(token));
320           }// End while
321           fm.put(attName, list);
322         }// End if
323       else {
324         fm.put(atts.getQName(i), attValue);
325       }// End if
326     }// End for
327 
328     // create the START index of the annotation
329     Long startIndex = new Long(tmpDocContent.length());
330 
331     // initialy the Start index is equal with End index
332     CustomObject obj = new CustomObject(customObjectId, elemName, fm,
333             startIndex, startIndex);
334 
335     // put this object into the stack
336     stack.push(obj);
337   }// startElement();
338 
339 
340   /**
341    * This method is called when the SAX parser encounts the end of an
342    * XML element.
343    * Here we extract
344    */
345   /**
346    *
347    @param uri - namespace uri
348    @param localName - local, unprefixed element name
349    @param qName - fully qualified, prefixed element name
350    @throws SAXException
351    */
352   @Override
353   public void endElement(String uri, String localName, String qName)
354           throws SAXException {
355 
356     /** Use localName rather than qName if global flag is set */
357     String elemName = qName;
358     boolean hasNSUri = (uri != null && !uri.isEmpty());
359     if (deserializeNamespaceInfo && hasNSUri)
360       elemName = localName;
361 
362     // call characterActions
363     if (readCharacterStatus) {
364       readCharacterStatus = false;
365       charactersAction(new String(contentBuffer).toCharArray()0, contentBuffer.length());
366     }
367 
368     // obj is for internal use
369     CustomObject obj = null;
370 
371     // if the stack is not empty, we extract the custom object and
372     // delete it from the stack
373     if(!stack.isEmpty()) {
374       obj = stack.pop();
375 
376       // Before adding it to the colector, we need to check if is an
377       // emptyAndSpan one. See CustomObject's isEmptyAndSpan field.
378       if(obj.getStart().equals(obj.getEnd())) {
379         // The element had an end tag and its start was equal to its
380         // end. Hence it is anEmptyAndSpan one.
381         obj.getFM().put("isEmptyAndSpan""true");
382       }// End iff
383 
384       // Put the object into colector. Later, when the document ends
385       // we will use colector to create all the annotations
386       colector.add(obj);
387     }// End if
388 
389     // if element is found on Element2String map, then add the string to the
390     // end of the document content
391     if (element2StringMap != null) {
392       String stringFromMap = null;
393 
394       // test to see if element is inside the map
395       // if it is then get the string value and add it to the document content
396       stringFromMap = element2StringMap.get(elemName);
397       if (stringFromMap != null) {
398         tmpDocContent.append(stringFromMap);
399       }
400     }// End if
401   }// endElement();
402 
403   /**
404    * This method is called when the SAX parser encounts text in the XML doc.
405    * Here we calculate the end indices for all the elements present inside the
406    * stack and update with the new values. For entities, this method is called
407    * separatley regardless of the text sourinding the entity.
408    */
409   @Override
410   public void characters(char[] text, int start, int lengththrows SAXException {
411     if (!readCharacterStatus) {
412       contentBuffer = new StringBuffer(new String(text, start, length));
413     else {
414       contentBuffer.append(new String(text, start, length));
415     }
416     readCharacterStatus = true;
417   }
418 
419   /**
420    * This method is called when all characters between specific tags have been read completely
421    */
422   public void charactersAction(char[] text, int start, int lengththrows SAXException {
423     // correction of real offset. Didn't affect on other data.
424     super.characters(text, start, length);
425     // create a string object based on the reported text
426     String content = new String(text, start, length);
427     StringBuffer contentBuffer = new StringBuffer("");
428     int tmpDocContentSize = tmpDocContent.length();
429     boolean incrementStartIndex = false;
430     boolean addExtraSpace = true;
431     if (Gate.getUserConfig().get(
432             GateConstants.DOCUMENT_ADD_SPACE_ON_UNPACK_FEATURE_NAME!= null) {
433       addExtraSpace =
434               Gate.getUserConfig().getBoolean(
435               GateConstants.DOCUMENT_ADD_SPACE_ON_UNPACK_FEATURE_NAME).booleanValue();
436     }
437     // If the first char of the text just read "text[0]" is NOT whitespace AND
438     // the last char of the tmpDocContent[SIZE-1] is NOT whitespace then
439     // concatenation "tmpDocContent + content" will result into a new different
440     // word... and we want to avoid that, because the tokenizer, gazetter and
441     // Jape work on the raw text and concatenating tokens might be not good.
442     if (tmpDocContentSize != &&
443             content.length() != &&
444             !Character.isWhitespace(content.charAt(0)) &&
445             !Character.isWhitespace(tmpDocContent.charAt(tmpDocContentSize - 1))) {
446 
447       // If we are here it means that a concatenation between the last
448       // token in the tmpDocContent and the content(which doesn't start
449       // with a white space) will be performed. In order to prevent this,
450       // we will add a " " space char in order to assure that the 2 tokens
451       // stay apart. Howerver we will except from this rule the most known
452       // internal entities like &, <, >, etc
453       if (( // Testing the length against 1 makes it more likely that
454               // an internal entity was called. characters() gets called for
455               // each entity separately.
456               (content.length() == 1&&
457               (content.charAt(0== '&' ||
458               content.charAt(0== '<' ||
459               content.charAt(0== '>' ||
460               content.charAt(0== '"' ||
461               content.charAt(0== '\'')) ||
462               (tmpDocContent.charAt(tmpDocContentSize - 1== '&' ||
463               tmpDocContent.charAt(tmpDocContentSize - 1== '<' ||
464               tmpDocContent.charAt(tmpDocContentSize - 1== '>' ||
465               tmpDocContent.charAt(tmpDocContentSize - 1== '"' ||
466               tmpDocContent.charAt(tmpDocContentSize - 1== '\'')) {// do nothing. The content will be appended
467       else if (!addExtraSpace) {
468       else {
469         // In all other cases append " "
470         contentBuffer.append(" ");
471         incrementStartIndex = true;
472       }// End if
473     }// End if
474 
475     // put the repositioning information
476     if (reposInfo != null) {
477       if (!(start == && length == && text.length <= 2)) {
478         // normal piece of text
479         reposInfo.addPositionInfo(getRealOffset(), content.length(),
480                 tmpDocContent.length() + contentBuffer.length(),
481                 content.length());
482         if (DEBUG) {
483           Out.println("Info: " + getRealOffset() ", " + content.length());
484           Out.println("Start: " + start + " len" + length);
485         // DEBUG
486       else {
487         // unicode char or &xxx; coding
488         // Reported from the parser offset is 0
489         // The real offset should be found in the ampCodingInfo structure.
490 
491         long lastPosition = 0;
492         RepositioningInfo.PositionInfo pi;
493 
494         if (reposInfo.size() 0) {
495           pi = reposInfo.get(reposInfo.size() 1);
496           lastPosition = pi.getOriginalPosition();
497         // if
498 
499         for (int i = 0; i < ampCodingInfo.size(); ++i) {
500           pi = ampCodingInfo.get(i);
501           if (pi.getOriginalPosition() > lastPosition) {
502             // found
503             reposInfo.addPositionInfo(pi.getOriginalPosition(),
504                     pi.getOriginalLength(),
505                     tmpDocContent.length() + contentBuffer.length(),
506                     content.length());
507             break;
508           // if
509         // for
510       // if
511     // if
512 
513     // update the document content
514     contentBuffer.append(content);
515     // calculate the End index for all the elements of the stack
516     // the expression is : End index = Current doc length + text length
517     Long end = new Long(tmpDocContent.length() + contentBuffer.length());
518 
519     CustomObject obj = null;
520     // Iterate through stack to modify the End index of the existing elements
521 
522     Iterator<CustomObject> anIterator = stack.iterator();
523     while (anIterator.hasNext()) {
524       // get the object and move to the next one
525       obj = anIterator.next();
526       if (incrementStartIndex && obj.getStart().equals(obj.getEnd())) {
527         obj.setStart(new Long(obj.getStart().longValue() 1));
528       }// End if
529       // sets its End index
530       obj.setEnd(end);
531     }// End while
532 
533     tmpDocContent.append(contentBuffer.toString());
534   }// characters();
535 
536   /**
537    * This method is called when the SAX parser encounts white spaces
538    */
539   @Override
540   public void ignorableWhitespace(char ch[]int start, int lengththrows
541           SAXException {
542 
543     // internal String object
544     String text = new String(ch, start, length);
545     // if the last character in tmpDocContent is \n and the read whitespace is
546     // \n then don't add it to tmpDocContent...
547 
548     if (tmpDocContent.length() != 0) {
549       if (tmpDocContent.charAt(tmpDocContent.length() 1!= '\n' ||
550               !text.equalsIgnoreCase("\n")) {
551         tmpDocContent.append(text);
552       }
553     }
554   }
555 
556   /**
557    * Error method.We deal with this exception inside SimpleErrorHandler class
558    */
559   @Override
560   public void error(SAXParseException exthrows SAXException {
561     // deal with a SAXParseException
562     // see SimpleErrorhandler class
563     _seh.error(ex);
564   }
565 
566   /**
567    * FatalError method.
568    */
569   @Override
570   public void fatalError(SAXParseException exthrows SAXException {
571     // deal with a SAXParseException
572     // see SimpleErrorhandler class
573     _seh.fatalError(ex);
574   }
575 
576   /**
577    * Warning method comment.
578    */
579   @Override
580   public void warning(SAXParseException exthrows SAXException {
581     // deal with a SAXParseException
582     // see SimpleErrorhandler class
583     _seh.warning(ex);
584   }
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 textthrows SAXException {
592     // create a FeatureMap and then add the comment to the annotation set.
593     /*
594     gate.util.SimpleFeatureMapImpl fm = new gate.util.SimpleFeatureMapImpl();
595     fm.put ("text_comment",text);
596     Long node = new Long (tmpDocContent.length());
597     CustomObject anObject = new CustomObject("Comment",fm,node,node);
598     colector.add(anObject);
599      */
600   }
601 
602   /**
603    * This method is called when the SAX parser encounts a start of a CDATA
604    * section
605    * It works only if the XmlDocumentHandler implements a
606    * com.sun.parser.LexicalEventListener
607    */
608   public void startCDATA() throws SAXException {
609   }
610 
611   /**
612    * This method is called when the SAX parser encounts the end of a CDATA
613    * section.
614    * It works only if the XmlDocumentHandler implements a
615    * com.sun.parser.LexicalEventListener
616    */
617   public void endCDATA() throws SAXException {
618   }
619 
620   /**
621    * This method is called when the SAX parser encounts a parsed Entity
622    * It works only if the XmlDocumentHandler implements a
623    * com.sun.parser.LexicalEventListener
624    */
625   public void startParsedEntity(String namethrows SAXException {
626   }
627 
628   /**
629    * This method is called when the SAX parser encounts a parsed entity and
630    * informs the application if that entity was parsed or not
631    * It's working only if the CustomDocumentHandler implements a
632    *  com.sun.parser.LexicalEventListener
633    */
634   public void endParsedEntity(String name, boolean includedthrows SAXException {
635   }
636 
637   //StatusReporter Implementation
638   /**
639    * This methos is called when a listener is registered with this class
640    */
641   public void addStatusListener(StatusListener listener) {
642     myStatusListeners.add(listener);
643   }
644 
645   /**
646    * This methos is called when a listener is removed
647    */
648   public void removeStatusListener(StatusListener listener) {
649     myStatusListeners.remove(listener);
650   }
651 
652   /**
653    * This methos is called whenever we need to inform the listener about an
654    * event.
655    */
656   protected void fireStatusChangedEvent(String text) {
657     Iterator<StatusListener> listenersIter = myStatusListeners.iterator();
658     while (listenersIter.hasNext()) {
659       listenersIter.next().statusChanged(text);
660     }
661   }
662 
663   /** This method is a workaround of the java 4 non namespace supporting parser
664    * It receives a qualified name and returns its local name.
665    * For eg. if it receives gate:gateId it will return gateId
666    */
667   @SuppressWarnings("unused")
668   private String getMyLocalName(String aQName) {
669     if (aQName == null) {
670       return "";
671     }
672     StringTokenizer strToken = new StringTokenizer(aQName, ":");
673     if (strToken.countTokens() <= 1) {
674       return aQName;
675     }
676     // The nr of tokens is >= than 2
677     // Skip the first token which is the QName
678     strToken.nextToken();
679     return strToken.nextToken();
680   }//getMyLocalName()
681 
682   /** Also a workaround for URI identifier. If the QName is gate it will return
683    *  GATE's. Otherwhise it will return the empty string
684    */
685   @SuppressWarnings("unused")
686   private String getMyURI(String aQName) {
687     if (aQName == null) {
688       return "";
689     }
690     StringTokenizer strToken = new StringTokenizer(aQName, ":");
691     if (strToken.countTokens() <= 1) {
692       return "";
693     }
694     // If first token is "gate" then return GATE's URI
695     if ("gate".equalsIgnoreCase(strToken.nextToken())) {
696       return Gate.URI;
697     }
698     return "";
699   }// getMyURI()
700   // XmlDocumentHandler member data
701   // this constant indicates when to fire the status listener
702   // this listener will add an overhead and we don't want a big overhead
703   // this listener will be callled from ELEMENTS_RATE to ELEMENTS_RATE
704   final static int ELEMENTS_RATE = 128;
705   // this map contains the elements name that we want to create
706   // if it's null all the elements from the XML documents will be transformed
707   // into Gate annotation objects otherwise only the elements it contains will
708   // be transformed
709   private Map<String,String> markupElementsMap = null;
710   // this map contains the string that we want to insert iside the document
711   // content, when a certain element is found
712   // if the map is null then no string is added
713   private Map<String,String> element2StringMap = null;
714   /**This object inducates what to do when the parser encounts an error*/
715   private SimpleErrorHandler _seh = new SimpleErrorHandler();
716   /**The content of the XML document, without any tag for internal use*/
717   private StringBuffer tmpDocContent = null;
718   /**A stack used to remember elements and to keep the order */
719   private Stack<CustomObject> stack = null;
720   /**A gate document */
721   private gate.Document doc = null;
722   /**An annotation set used for creating annotation reffering the doc */
723   private gate.AnnotationSet basicAS = null;
724   /**Listeners for status report */
725   protected List<StatusListener> myStatusListeners = new LinkedList<StatusListener>();
726   /**This reports the the number of elements that have beed processed so far*/
727   private int elements = 0;
728   /** We need a colection to retain all the CustomObjects that will be
729    * transformed into annotation over the gate document...
730    * the transformation will take place inside onDocumentEnd() method
731    */
732   private LinkedList<CustomObject> colector = null;
733   /** This is used to generate unique Ids for the CustomObjects read*/
734   protected int customObjectsId = 0;
735 
736   /** Accesor method for the customObjectsId field*/
737   public int getCustomObjectsId() {
738     return customObjectsId;
739   }
740 
741   //////// INNER CLASS
742   /**
743    * The objects belonging to this class are used inside the stack.
744    * This class is for internal needs
745    */
746   class CustomObject implements Comparable<CustomObject> {
747 
748     // constructor
749     public CustomObject(Integer anId, String anElemName, FeatureMap aFm,
750             Long aStart, Long anEnd) {
751       elemName = anElemName;
752       fm = aFm;
753       start = aStart;
754       end = anEnd;
755       if (anId == null) {
756         id = new Integer(customObjectsId++);
757       else {
758         id = anId;
759         if (customObjectsId <= anId.intValue()) {
760           customObjectsId = anId.intValue() 1;
761         }
762       }// End if
763     }// End CustomObject()
764 
765     // Methos implemented as required by Comparable interface
766     @Override
767     public int compareTo(CustomObject obj) {
768       return this.id.compareTo(obj.getId());
769     }// compareTo();
770 
771     // accesor
772     public String getElemName() {
773       return elemName;
774     }// getElemName()
775 
776     public FeatureMap getFM() {
777       return fm;
778     }// getFM()
779 
780     public Long getStart() {
781       return start;
782     }// getStart()
783 
784     public Long getEnd() {
785       return end;
786     }// getEnd()
787 
788     public Integer getId() {
789       return id;
790     }
791 
792     // mutator
793     public void setElemName(String anElemName) {
794       elemName = anElemName;
795     }// getElemName()
796 
797     public void setFM(FeatureMap aFm) {
798       fm = aFm;
799     }// setFM();
800 
801     public void setStart(Long aStart) {
802       start = aStart;
803     }// setStart();
804 
805     public void setEnd(Long anEnd) {
806       end = anEnd;
807     }// setEnd();
808     // data fields
809     private String elemName = null;
810     private FeatureMap fm = null;
811     private Long start = null;
812     private Long end = null;
813     private Integer id = null;
814   // End inner class CustomObject
815 //XmlDocumentHandler
816 
817