XmlDocumentFormat.java
001 /*
002  *  XmlDocumentFormat.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, 26/May/2000
013  *
014  *  $Id: XmlDocumentFormat.java 19676 2016-10-14 05:51:04Z markagreenwood $
015  */
016 
017 package gate.corpora;
018 
019 // import com.sun.xml.parser.* ;
020 import gate.Document;
021 import gate.GateConstants;
022 import gate.Resource;
023 import gate.TextualDocument;
024 import gate.creole.ResourceInstantiationException;
025 import gate.creole.metadata.AutoInstance;
026 import gate.creole.metadata.CreoleResource;
027 import gate.event.StatusListener;
028 import gate.util.DocumentFormatException;
029 import gate.util.Out;
030 import gate.xml.XmlDocumentHandler;
031 
032 import java.io.IOException;
033 import java.io.InputStream;
034 import java.io.InputStreamReader;
035 import java.io.Reader;
036 import java.io.StringReader;
037 import java.io.UnsupportedEncodingException;
038 
039 import javax.xml.parsers.ParserConfigurationException;
040 import javax.xml.parsers.SAXParser;
041 import javax.xml.parsers.SAXParserFactory;
042 import javax.xml.stream.XMLInputFactory;
043 import javax.xml.stream.XMLStreamException;
044 import javax.xml.stream.XMLStreamReader;
045 
046 import org.apache.commons.io.IOUtils;
047 import org.xml.sax.InputSource;
048 import org.xml.sax.SAXException;
049 
050 // import org.w3c.www.mime.*;
051 
052 /**
053  * The format of Documents. Subclasses of DocumentFormat know about
054  * particular MIME types and how to unpack the information in any markup
055  * or formatting they contain into GATE annotations. Each MIME type has
056  * its own subclass of DocumentFormat, e.g. XmlDocumentFormat,
057  * RtfDocumentFormat, MpegDocumentFormat. These classes register
058  * themselves with a static index residing here when they are
059  * constructed. Static getDocumentFormat methods can then be used to get
060  * the appropriate format class for a particular document.
061  */
062 @CreoleResource(name = "GATE XML Document Format", isPrivate = true,
063     autoinstances = {@AutoInstance(hidden = true)})
064 public class XmlDocumentFormat extends TextualDocumentFormat {
065 
066   private static final long serialVersionUID = 3205973554326782116L;
067 
068   /**
069    * InputFactory for the StAX parser used for GATE format XML.
070    */
071   private static XMLInputFactory staxFactory;
072 
073   /** Default construction */
074   public XmlDocumentFormat() {
075     super();
076   }
077 
078   /** We could collect repositioning information during XML parsing */
079   @Override
080   public Boolean supportsRepositioning() {
081     return new Boolean(true);
082   // supportsRepositioning
083 
084   /** Old style of unpackMarkup (without collecting of RepositioningInfo) */
085   @Override
086   public void unpackMarkup(Document docthrows DocumentFormatException {
087     unpackMarkup(doc, (RepositioningInfo)null, (RepositioningInfo)null);
088   // unpackMarkup
089 
090   /**
091    * Unpack the markup in the document. This converts markup from the
092    * native format (e.g. XML) into annotations in GATE format. Uses the
093    * markupElementsMap to determine which elements to convert, and what
094    * annotation type names to use. If the document was created from a
095    * String, then is recomandable to set the doc's sourceUrl to <b>null</b>.
096    * So, if the document has a valid URL, then the parser will try to
097    * parse the XML document pointed by the URL.If the URL is not valid,
098    * or is null, then the doc's content will be parsed. If the doc's
099    * content is not a valid XML then the parser might crash.
100    *
101    @param doc The gate document you want to parse. If
102    *          <code>doc.getSourceUrl()</code> returns <b>null</b>
103    *          then the content of doc will be parsed. Using a URL is
104    *          recomended because the parser will report errors corectlly
105    *          if the XML document is not well formed.
106    */
107   @Override
108   public void unpackMarkup(Document doc, RepositioningInfo repInfo,
109           RepositioningInfo ampCodingInfothrows DocumentFormatException {
110     if((doc == null)
111             || (doc.getSourceUrl() == null && doc.getContent() == null)) {
112 
113       throw new DocumentFormatException(
114               "GATE document is null or no content found. Nothing to parse!");
115     }// End if
116 
117     // Create a status listener
118     StatusListener statusListener = new StatusListener() {
119       @Override
120       public void statusChanged(String text) {
121         // This is implemented in DocumentFormat.java and inherited here
122         fireStatusChanged(text);
123       }
124     };
125 
126     // determine whether we have a GATE format XML document or another
127     // kind
128     /*String content = doc.getContent().toString();
129     if(content.length() > 2048) {
130       content = content.substring(0, 2048);
131     }*/
132     boolean gateFormat = isGateXmlFormat(doc);
133 
134     if(gateFormat) {
135       unpackGateFormatMarkup(doc, statusListener);
136     }
137     else {
138       unpackGeneralXmlMarkup(doc, repInfo, ampCodingInfo, statusListener);
139     }
140   }
141 
142   /**
143    * Unpacks markup in the GATE-specific standoff XML markup format.
144    *
145    @param doc the document to process
146    @param statusListener optional status listener to receive status
147    *          messages
148    @throws DocumentFormatException if a fatal error occurs during
149    *           parsing
150    */
151   private void unpackGateFormatMarkup(Document doc,
152           StatusListener statusListenerthrows DocumentFormatException {
153     boolean docHasContentButNoValidURL = hasContentButNoValidUrl(doc);
154 
155     try {
156       Reader inputReader = null;
157       InputStream inputStream = null;
158       XMLStreamReader xsr = null;
159       if(docHasContentButNoValidURL) {
160         inputReader = new StringReader(doc.getContent().toString());
161         xsr = getInputFactory().createXMLStreamReader(inputReader);
162       }
163       else if(doc instanceof TextualDocument) {
164         String encoding = ((TextualDocument)doc).getEncoding();
165         // Don't strip BOM on XML.
166         inputReader = new InputStreamReader(doc.getSourceUrl().openStream(),
167                 encoding);
168         // create stream reader with the URL as system ID, to support
169         // relative URLs to e.g. DTD or external entities
170         xsr = getInputFactory().createXMLStreamReader(
171                 doc.getSourceUrl().toExternalForm(), inputReader);
172       }
173       else {
174         // not a TextualDocument, so let parser determine encoding
175         inputStream = doc.getSourceUrl().openStream();
176         xsr = getInputFactory().createXMLStreamReader(
177                 doc.getSourceUrl().toExternalForm(), inputStream);
178       }
179 
180       // find the opening GateDocument tag
181       xsr.nextTag();
182 
183       // parse the document
184       try {
185         DocumentStaxUtils.readGateXmlDocument(xsr, doc, statusListener);
186       }
187       finally {
188         xsr.close();
189         if(inputStream != null) {
190           inputStream.close();
191         }
192         if(inputReader != null) {
193           inputReader.close();
194         }
195       }
196     }
197     catch(XMLStreamException e) {
198       doc.getFeatures().put("parsingError", Boolean.TRUE);
199 
200       Boolean bThrow = (Boolean)doc.getFeatures().get(
201               GateConstants.THROWEX_FORMAT_PROPERTY_NAME);
202 
203       if(bThrow != null && bThrow.booleanValue()) {
204         // the next line is commented to avoid Document creation fail on
205         // error
206         throw new DocumentFormatException(e);
207       }
208       else {
209         Out.println("Warning: Document remains unparsed. \n"
210                 "\n  Stack Dump: ");
211         e.printStackTrace(Out.getPrintWriter());
212       // if
213     }
214     catch(IOException ioe) {
215       throw new DocumentFormatException("I/O exception for "
216               + doc.getSourceUrl().toString(), ioe);
217     }
218   }
219 
220   /**
221    * Returns the StAX input factory, creating one if it is currently
222    * null.
223    *
224    @return <code>staxFactory</code>
225    @throws XMLStreamException
226    */
227   private static XMLInputFactory getInputFactory() throws XMLStreamException {
228     if(staxFactory == null) {
229       staxFactory = XMLInputFactory.newInstance();
230       staxFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
231       staxFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
232       staxFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,
233               Boolean.TRUE);
234       staxFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
235               Boolean.TRUE);
236     }
237     return staxFactory;
238   }
239 
240   /**
241    * Unpack markup from any XML format. The XML elements are translated
242    * to annotations on the Original markups annotation set.
243    *
244    @param doc the document to process
245    @throws DocumentFormatException
246    */
247   private void unpackGeneralXmlMarkup(Document doc, RepositioningInfo repInfo,
248           RepositioningInfo ampCodingInfo, StatusListener statusListener)
249           throws DocumentFormatException {
250     boolean docHasContentButNoValidURL = hasContentButNoValidUrl(doc);
251 
252     XmlDocumentHandler xmlDocHandler = null;
253     try {
254       // use Xerces XML parser with JAXP
255       // System.setProperty("javax.xml.parsers.SAXParserFactory",
256       // "org.apache.xerces.jaxp.SAXParserFactoryImpl");
257       // Get a parser factory.
258       SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
259       // Set up the factory to create the appropriate type of parser
260       // non validating one
261       saxParserFactory.setValidating(false);
262       // non namesapace aware one
263       saxParserFactory.setNamespaceAware(true);
264       // create it
265       SAXParser xmlParser = saxParserFactory.newSAXParser();
266 
267       // Create a new Xml document handler
268       xmlDocHandler = new XmlDocumentHandler(doc, this.markupElementsMap,
269               this.element2StringMap);
270       // Register a status listener with it
271       xmlDocHandler.addStatusListener(statusListener);
272       // set repositioning object
273       xmlDocHandler.setRepositioningInfo(repInfo);
274       // set the object with ampersand coding positions
275       xmlDocHandler.setAmpCodingInfo(ampCodingInfo);
276 
277       org.xml.sax.XMLReader newxmlParser = xmlParser.getXMLReader();
278       // Set up the factory to create the appropriate type of parser
279       // non validating one
280       // http://xml.org/sax/features/validation set to false
281       newxmlParser.setFeature("http://xml.org/sax/features/validation"false);
282       // namesapace aware one
283       // http://xml.org/sax/features/namespaces set to true
284       newxmlParser.setFeature("http://xml.org/sax/features/namespaces"true);
285       newxmlParser.setFeature("http://xml.org/sax/features/namespace-prefixes",
286               true);
287       newxmlParser.setContentHandler(xmlDocHandler);
288       newxmlParser.setErrorHandler(xmlDocHandler);
289       newxmlParser.setDTDHandler(xmlDocHandler);
290       newxmlParser.setEntityResolver(xmlDocHandler);
291       // Parse the XML Document with the appropriate encoding
292       Reader docReader = null;
293       try{
294         InputSource is;
295         if(docHasContentButNoValidURL) {
296           // no URL, so parse from string
297           is = new InputSource(new StringReader(doc.getContent().toString()));
298         }
299         else if(doc instanceof TextualDocument) {
300           // textual document - load with user specified encoding
301           String docEncoding = ((TextualDocument)doc).getEncoding();
302           // don't strip BOM on XML.
303           docReader = new InputStreamReader(doc.getSourceUrl()
304                   .openStream(), docEncoding);
305           is = new InputSource(docReader);
306           // must set system ID to allow relative URLs (e.g. to a DTD) to
307           // work
308           is.setSystemId(doc.getSourceUrl().toString());
309         }
310         else {
311           // let the parser decide the encoding
312           is = new InputSource(doc.getSourceUrl().toString());
313         }
314         newxmlParser.parse(is);
315       }finally{
316         //make sure the open streams are closed
317         if(docReader != nulldocReader.close();
318       }
319       // Angel - end
320       ((DocumentImpl)doc).setNextAnnotationId(xmlDocHandler
321               .getCustomObjectsId());
322     }
323     catch(ParserConfigurationException e) {
324       throw new DocumentFormatException("XML parser configuration exception ",
325               e);
326     }
327     catch(SAXException e) {
328       doc.getFeatures().put("parsingError", Boolean.TRUE);
329 
330       Boolean bThrow = (Boolean)doc.getFeatures().get(
331               GateConstants.THROWEX_FORMAT_PROPERTY_NAME);
332 
333       if(bThrow != null && bThrow.booleanValue()) {
334         // the next line is commented to avoid Document creation fail on
335         // error
336         throw new DocumentFormatException(e);
337       }
338       else {
339         Out.println("Warning: Document remains unparsed. \n"
340                 "\n  Stack Dump: ");
341         e.printStackTrace(Out.getPrintWriter());
342       // if
343 
344     }
345     catch(IOException e) {
346       throw new DocumentFormatException("I/O exception for "
347               + doc.getSourceUrl(), e);
348     }
349     finally {
350       if(xmlDocHandler != null)
351         xmlDocHandler.removeStatusListener(statusListener);
352     }// End if else try
353   }// unpackMarkup
354 
355   /**
356    * Determine whether the given document content string represents a
357    * GATE custom format XML document.
358    */
359   @Deprecated
360   protected static boolean isGateXmlFormat(String content) {
361     return (content.indexOf("<GateDocument"!= -|| content
362             .indexOf(" GateDocument"!= -1);
363   }
364   
365   protected static boolean isGateXmlFormat(Document doc)
366           throws DocumentFormatException {
367 
368     try {
369       byte[] header = new byte[2048];
370 
371       if(hasContentButNoValidUrl(doc)) {
372         String content = doc.getContent().toString();
373         if(content.length() 2048) {
374           content = content.substring(02048);
375         }
376         header = content.getBytes(((TextualDocument)doc).getEncoding());
377       else {
378         IOUtils.read(doc.getSourceUrl().openStream(), header);
379       }
380 
381       int index = indexOf(header,
382               "GateDocument".getBytes(((TextualDocument)doc).getEncoding()));
383 
384       return index != -1;
385     catch(IOException e) {
386       throw new DocumentFormatException(e);
387     }
388   }
389   
390   /**
391    * Finds the first occurrence of the pattern in the text.
392    */
393   protected static int indexOf(byte[] data, byte[] pattern) {
394       int[] failure = computeFailure(pattern);
395 
396       int j = 0;
397       if (data.length == 0return -1;
398 
399       for (int i = 0; i < data.length; i++) {
400           while (j > && pattern[j!= data[i]) {
401               j = failure[j - 1];
402           }
403           if (pattern[j== data[i]) { j++; }
404           if (j == pattern.length) {
405               return i - pattern.length + 1;
406           }
407       }
408       return -1;
409   }
410 
411   /**
412    * Computes the failure function using a boot-strapping process,
413    * where the pattern is matched against itself.
414    */
415   private static int[] computeFailure(byte[] pattern) {
416       int[] failure = new int[pattern.length];
417 
418       int j = 0;
419       for (int i = 1; i < pattern.length; i++) {
420           while (j > && pattern[j!= pattern[i]) {
421               j = failure[j - 1];
422           }
423           if (pattern[j== pattern[i]) {
424               j++;
425           }
426           failure[i= j;
427       }
428 
429       return failure;
430   }
431 
432   /** Initialise this resource, and return it. */
433   @Override
434   public Resource init() throws ResourceInstantiationException {
435     // Register XML mime type
436     MimeType mime = new MimeType("text""xml");
437     // Register the class handler for this mime type
438     mimeString2ClassHandlerMap.put(mime.getType() "/" + mime.getSubtype(),
439             this);
440     // Register the mime type with mine string
441     mimeString2mimeTypeMap.put(mime.getType() "/" + mime.getSubtype(), mime);
442     // sometimes XML file appear as application/xml
443     mimeString2mimeTypeMap.put("application/xml", mime);
444     // Register file sufixes for this mime type
445     suffixes2mimeTypeMap.put("xml", mime);
446     suffixes2mimeTypeMap.put("xhtm", mime);
447     suffixes2mimeTypeMap.put("xhtml", mime);
448     // Register magic numbers for this mime type
449     magic2mimeTypeMap.put("<?xml", mime);
450     // Set the mimeType for this language resource
451     setMimeType(mime);
452     return this;
453   }// init()
454 
455 }// class XmlDocumentFormat