1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 // ResolvingParser.java - An interface for reading catalog files
   6 
   7 /*
   8  * Copyright 2001-2004 The Apache Software Foundation or its licensors,
   9  * as applicable.
  10  *
  11  * Licensed under the Apache License, Version 2.0 (the "License");
  12  * you may not use this file except in compliance with the License.
  13  * You may obtain a copy of the License at
  14  *
  15  *      http://www.apache.org/licenses/LICENSE-2.0
  16  *
  17  * Unless required by applicable law or agreed to in writing, software
  18  * distributed under the License is distributed on an "AS IS" BASIS,
  19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20  * See the License for the specific language governing permissions and
  21  * limitations under the License.
  22  */
  23 
  24 package com.sun.org.apache.xml.internal.resolver.tools;
  25 
  26 import java.io.IOException;
  27 import java.io.InputStream;
  28 import java.net.URL;
  29 import java.net.MalformedURLException;
  30 import java.util.Locale;
  31 
  32 import org.xml.sax.Parser;
  33 import org.xml.sax.InputSource;
  34 import org.xml.sax.Locator;
  35 import org.xml.sax.ErrorHandler;
  36 import org.xml.sax.DTDHandler;
  37 import org.xml.sax.DocumentHandler;
  38 import org.xml.sax.AttributeList;
  39 import org.xml.sax.EntityResolver;
  40 import org.xml.sax.SAXException;
  41 
  42 import javax.xml.parsers.SAXParserFactory;
  43 import javax.xml.parsers.SAXParser;
  44 
  45 import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
  46 import com.sun.org.apache.xml.internal.resolver.Catalog;
  47 import com.sun.org.apache.xml.internal.resolver.CatalogManager;
  48 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
  49 
  50 /**
  51  * A SAX Parser that performs catalog-based entity resolution.
  52  *
  53  * <p>This class implements a SAX Parser that performs entity resolution
  54  * using the CatalogResolver. The actual, underlying parser is obtained
  55  * from a SAXParserFactory.</p>
  56  * </p>
  57  *
  58  * @deprecated This interface has been replaced by the
  59  *             {@link com.sun.org.apache.xml.internal.resolver.tools.ResolvingXMLReader} for SAX2.
  60  * @see CatalogResolver
  61  * @see org.xml.sax.Parser
  62  *
  63  * @author Norman Walsh
  64  * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
  65  *
  66  * @version 1.0
  67  */
  68 public class ResolvingParser
  69   implements Parser, DTDHandler, DocumentHandler, EntityResolver {
  70   /** Make the parser Namespace aware? */
  71   public static boolean namespaceAware = true;
  72 
  73   /** Make the parser validating? */
  74   public static boolean validating = false;
  75 
  76   /** Suppress explanatory message?
  77    *
  78    * @see #parse(InputSource)
  79    */
  80   public static boolean suppressExplanation = false;
  81 
  82   /** The underlying parser. */
  83   private SAXParser saxParser = null;
  84 
  85   /** The underlying reader. */
  86   private Parser parser = null;
  87 
  88   /** The underlying DocumentHandler. */
  89   private DocumentHandler documentHandler = null;
  90 
  91   /** The underlying DTDHandler. */
  92   private DTDHandler dtdHandler = null;
  93 
  94   /** The manager for the underlying resolver. */
  95   private CatalogManager catalogManager = CatalogManager.getStaticManager();
  96 
  97   /** The underlying catalog resolver. */
  98   private CatalogResolver catalogResolver = null;
  99 
 100   /** A separate resolver for oasis-xml-pi catalogs. */
 101   private CatalogResolver piCatalogResolver = null;
 102 
 103   /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */
 104   private boolean allowXMLCatalogPI = false;
 105 
 106   /** Has an oasis-xml-catalog PI been seen? */
 107   private boolean oasisXMLCatalogPI = false;
 108 
 109   /** The base URI of the input document, if known. */
 110   private URL baseURL = null;
 111 
 112   /** Constructor. */
 113   public ResolvingParser() {
 114     initParser();
 115   }
 116 
 117   /** Constructor. */
 118   public ResolvingParser(CatalogManager manager) {
 119     catalogManager = manager;
 120     initParser();
 121   }
 122 
 123   /** Initialize the parser. */
 124   private void initParser() {
 125     catalogResolver = new CatalogResolver(catalogManager);
 126     SAXParserFactory spf = catalogManager.useServicesMechanism() ?
 127                     SAXParserFactory.newInstance() : new SAXParserFactoryImpl();
 128     spf.setNamespaceAware(namespaceAware);
 129     spf.setValidating(validating);
 130 
 131     try {
 132       saxParser = spf.newSAXParser();
 133       parser = saxParser.getParser();
 134       documentHandler = null;
 135       dtdHandler = null;
 136     } catch (Exception ex) {
 137       ex.printStackTrace();
 138     }
 139   }
 140 
 141   /** Return the Catalog being used. */
 142   public Catalog getCatalog() {
 143     return catalogResolver.getCatalog();
 144   }
 145 
 146   /**
 147    * SAX Parser API.
 148    *
 149    * <p>Note that the JAXP 1.1ea2 parser crashes with an InternalError if
 150    * it encounters a system identifier that appears to be a relative URI
 151    * that begins with a slash. For example, the declaration:</p>
 152    *
 153    * <pre>
 154    * &lt;!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd">
 155    * </pre>
 156    *
 157    * <p>would cause such an error. As a convenience, this method catches
 158    * that error and prints an explanation. (Unfortunately, it's not possible
 159    * to identify the particular system identifier that causes the problem.)
 160    * </p>
 161    *
 162    * <p>The underlying error is forwarded after printing the explanatory
 163    * message. The message is only every printed once and if
 164    * <code>suppressExplanation</code> is set to <code>false</code> before
 165    * parsing, it will never be printed.</p>
 166    */
 167   public void parse(InputSource input)
 168     throws IOException,
 169            SAXException {
 170     setupParse(input.getSystemId());
 171     try {
 172       parser.parse(input);
 173     } catch (InternalError ie) {
 174       explain(input.getSystemId());
 175       throw ie;
 176     }
 177   }
 178 
 179   /** SAX Parser API.
 180    *
 181    * @see #parse(InputSource)
 182    */
 183   public void parse(String systemId)
 184     throws IOException,
 185            SAXException {
 186     setupParse(systemId);
 187     try {
 188       parser.parse(systemId);
 189     } catch (InternalError ie) {
 190       explain(systemId);
 191       throw ie;
 192     }
 193   }
 194 
 195   /** SAX Parser API. */
 196   public void setDocumentHandler(DocumentHandler handler) {
 197     documentHandler = handler;
 198   }
 199 
 200   /** SAX Parser API. */
 201   public void setDTDHandler(DTDHandler handler) {
 202     dtdHandler = handler;
 203   }
 204 
 205   /**
 206    * SAX Parser API.
 207    *
 208    * <p>The purpose of this class is to implement an entity resolver.
 209    * Attempting to set a different one is pointless (and ignored).</p>
 210    */
 211   public void setEntityResolver(EntityResolver resolver) {
 212     // nop
 213   }
 214 
 215   /** SAX Parser API. */
 216   public void setErrorHandler(ErrorHandler handler) {
 217     parser.setErrorHandler(handler);
 218   }
 219 
 220   /** SAX Parser API. */
 221   public void setLocale(Locale locale) throws SAXException {
 222     parser.setLocale(locale);
 223   }
 224 
 225   /** SAX DocumentHandler API. */
 226   public void characters(char[] ch, int start, int length)
 227     throws SAXException {
 228     if (documentHandler != null) {
 229       documentHandler.characters(ch,start,length);
 230     }
 231   }
 232 
 233   /** SAX DocumentHandler API. */
 234   public void endDocument() throws SAXException {
 235     if (documentHandler != null) {
 236       documentHandler.endDocument();
 237     }
 238   }
 239 
 240   /** SAX DocumentHandler API. */
 241   public void endElement(String name) throws SAXException {
 242     if (documentHandler != null) {
 243       documentHandler.endElement(name);
 244     }
 245   }
 246 
 247   /** SAX DocumentHandler API. */
 248   public void ignorableWhitespace(char[] ch, int start, int length)
 249     throws SAXException {
 250     if (documentHandler != null) {
 251       documentHandler.ignorableWhitespace(ch,start,length);
 252     }
 253   }
 254 
 255   /** SAX DocumentHandler API. */
 256   public void processingInstruction(String target, String pidata)
 257     throws SAXException {
 258 
 259     if (target.equals("oasis-xml-catalog")) {
 260       URL catalog = null;
 261       String data = pidata;
 262 
 263       int pos = data.indexOf("catalog=");
 264       if (pos >= 0) {
 265         data = data.substring(pos+8);
 266         if (data.length() > 1) {
 267           String quote = data.substring(0,1);
 268           data = data.substring(1);
 269           pos = data.indexOf(quote);
 270           if (pos >= 0) {
 271             data = data.substring(0, pos);
 272             try {
 273               if (baseURL != null) {
 274                 catalog = new URL(baseURL, data);
 275               } else {
 276                 catalog = new URL(data);
 277               }
 278             } catch (MalformedURLException mue) {
 279               // nevermind
 280             }
 281           }
 282         }
 283       }
 284 
 285       if (allowXMLCatalogPI) {
 286         if (catalogManager.getAllowOasisXMLCatalogPI()) {
 287           catalogManager.debug.message(4,"oasis-xml-catalog PI", pidata);
 288 
 289           if (catalog != null) {
 290             try {
 291               catalogManager.debug.message(4,"oasis-xml-catalog", catalog.toString());
 292               oasisXMLCatalogPI = true;
 293 
 294               if (piCatalogResolver == null) {
 295                 piCatalogResolver = new CatalogResolver(true);
 296               }
 297 
 298               piCatalogResolver.getCatalog().parseCatalog(catalog.toString());
 299             } catch (Exception e) {
 300               catalogManager.debug.message(3, "Exception parsing oasis-xml-catalog: "
 301                             + catalog.toString());
 302             }
 303           } else {
 304             catalogManager.debug.message(3, "PI oasis-xml-catalog unparseable: " + pidata);
 305           }
 306         } else {
 307           catalogManager.debug.message(4,"PI oasis-xml-catalog ignored: " + pidata);
 308         }
 309       } else {
 310         catalogManager.debug.message(3, "PI oasis-xml-catalog occurred in an invalid place: "
 311                       + pidata);
 312       }
 313     } else {
 314       if (documentHandler != null) {
 315         documentHandler.processingInstruction(target, pidata);
 316       }
 317     }
 318   }
 319 
 320   /** SAX DocumentHandler API. */
 321   public void setDocumentLocator(Locator locator) {
 322     if (documentHandler != null) {
 323       documentHandler.setDocumentLocator(locator);
 324     }
 325   }
 326 
 327   /** SAX DocumentHandler API. */
 328   public void startDocument() throws SAXException {
 329     if (documentHandler != null) {
 330       documentHandler.startDocument();
 331     }
 332   }
 333 
 334   /** SAX DocumentHandler API. */
 335   public void startElement(String name, AttributeList atts)
 336     throws SAXException {
 337     allowXMLCatalogPI = false;
 338     if (documentHandler != null) {
 339       documentHandler.startElement(name,atts);
 340     }
 341   }
 342 
 343   /** SAX DTDHandler API. */
 344   public void notationDecl (String name, String publicId, String systemId)
 345     throws SAXException {
 346     allowXMLCatalogPI = false;
 347     if (dtdHandler != null) {
 348       dtdHandler.notationDecl(name,publicId,systemId);
 349     }
 350   }
 351 
 352   /** SAX DTDHandler API. */
 353   public void unparsedEntityDecl (String name,
 354                                   String publicId,
 355                                   String systemId,
 356                                   String notationName)
 357     throws SAXException {
 358     allowXMLCatalogPI = false;
 359     if (dtdHandler != null) {
 360       dtdHandler.unparsedEntityDecl (name, publicId, systemId, notationName);
 361     }
 362   }
 363 
 364   /**
 365    * Implements the <code>resolveEntity</code> method
 366    * for the SAX interface, using an underlying CatalogResolver
 367    * to do the real work.
 368    */
 369   public InputSource resolveEntity (String publicId, String systemId) {
 370     allowXMLCatalogPI = false;
 371     String resolved = catalogResolver.getResolvedEntity(publicId, systemId);
 372 
 373     if (resolved == null && piCatalogResolver != null) {
 374       resolved = piCatalogResolver.getResolvedEntity(publicId, systemId);
 375     }
 376 
 377     if (resolved != null) {
 378       try {
 379         InputSource iSource = new InputSource(resolved);
 380         iSource.setPublicId(publicId);
 381 
 382         // Ideally this method would not attempt to open the
 383         // InputStream, but there is a bug (in Xerces, at least)
 384         // that causes the parser to mistakenly open the wrong
 385         // system identifier if the returned InputSource does
 386         // not have a byteStream.
 387         //
 388         // It could be argued that we still shouldn't do this here,
 389         // but since the purpose of calling the entityResolver is
 390         // almost certainly to open the input stream, it seems to
 391         // do little harm.
 392         //
 393         URL url = new URL(resolved);
 394         InputStream iStream = url.openStream();
 395         iSource.setByteStream(iStream);
 396 
 397         return iSource;
 398       } catch (Exception e) {
 399         catalogManager.debug.message(1, "Failed to create InputSource", resolved);
 400         return null;
 401       }
 402     } else {
 403       return null;
 404     }
 405   }
 406 
 407   /** Setup for parsing. */
 408   private void setupParse(String systemId) {
 409     allowXMLCatalogPI = true;
 410     parser.setEntityResolver(this);
 411     parser.setDocumentHandler(this);
 412     parser.setDTDHandler(this);
 413 
 414     URL cwd = null;
 415 
 416     try {
 417       cwd = FileURL.makeURL("basename");
 418     } catch (MalformedURLException mue) {
 419       cwd = null;
 420     }
 421 
 422     try {
 423       baseURL = new URL(systemId);
 424     } catch (MalformedURLException mue) {
 425       if (cwd != null) {
 426         try {
 427           baseURL = new URL(cwd, systemId);
 428         } catch (MalformedURLException mue2) {
 429           // give up
 430           baseURL = null;
 431         }
 432       } else {
 433         // give up
 434         baseURL = null;
 435       }
 436     }
 437   }
 438 
 439   /** Provide one possible explanation for an InternalError. */
 440   private void explain(String systemId) {
 441     if (!suppressExplanation) {
 442       System.out.println("Parser probably encountered bad URI in " + systemId);
 443       System.out.println("For example, replace '/some/uri' with 'file:/some/uri'.");
 444     }
 445   }
 446 }