1. /* $Id: Digester.java,v 1.104.2.1 2004/07/30 20:11:00 rdonkin Exp $
  2. *
  3. * Copyright 2001-2004 The Apache Software Foundation.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.commons.digester;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.Reader;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.util.EmptyStackException;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Properties;
  30. import javax.xml.parsers.ParserConfigurationException;
  31. import javax.xml.parsers.SAXParser;
  32. import javax.xml.parsers.SAXParserFactory;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. import org.apache.commons.collections.ArrayStack;
  36. import org.xml.sax.Attributes;
  37. import org.xml.sax.EntityResolver;
  38. import org.xml.sax.ErrorHandler;
  39. import org.xml.sax.InputSource;
  40. import org.xml.sax.Locator;
  41. import org.xml.sax.SAXException;
  42. import org.xml.sax.SAXNotRecognizedException;
  43. import org.xml.sax.SAXNotSupportedException;
  44. import org.xml.sax.SAXParseException;
  45. import org.xml.sax.XMLReader;
  46. import org.xml.sax.helpers.DefaultHandler;
  47. /**
  48. * <p>A <strong>Digester</strong> processes an XML input stream by matching a
  49. * series of element nesting patterns to execute Rules that have been added
  50. * prior to the start of parsing. This package was inspired by the
  51. * <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1,
  52. * but is organized somewhat differently.</p>
  53. *
  54. * <p>See the <a href="package-summary.html#package_description">Digester
  55. * Developer Guide</a> for more information.</p>
  56. *
  57. * <p><strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may
  58. * only be used within the context of a single thread at a time, and a call
  59. * to <code>parse()</code> must be completed before another can be initiated
  60. * even from the same thread.</p>
  61. *
  62. * <p><strong>IMPLEMENTATION NOTE</strong> - A bug in Xerces 2.0.2 prevents
  63. * the support of XML schema. You need Xerces 2.1/2.3 and up to make
  64. * this class working with XML schema</p>
  65. */
  66. public class Digester extends DefaultHandler {
  67. // --------------------------------------------------------- Constructors
  68. /**
  69. * Construct a new Digester with default properties.
  70. */
  71. public Digester() {
  72. super();
  73. }
  74. /**
  75. * Construct a new Digester, allowing a SAXParser to be passed in. This
  76. * allows Digester to be used in environments which are unfriendly to
  77. * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to
  78. * James House (james@interobjective.com). This may help in places where
  79. * you are able to load JAXP 1.1 classes yourself.
  80. */
  81. public Digester(SAXParser parser) {
  82. super();
  83. this.parser = parser;
  84. }
  85. /**
  86. * Construct a new Digester, allowing an XMLReader to be passed in. This
  87. * allows Digester to be used in environments which are unfriendly to
  88. * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
  89. * have to configure namespace and validation support yourself, as these
  90. * properties only affect the SAXParser and emtpy constructor.
  91. */
  92. public Digester(XMLReader reader) {
  93. super();
  94. this.reader = reader;
  95. }
  96. // --------------------------------------------------- Instance Variables
  97. /**
  98. * The body text of the current element.
  99. */
  100. protected StringBuffer bodyText = new StringBuffer();
  101. /**
  102. * The stack of body text string buffers for surrounding elements.
  103. */
  104. protected ArrayStack bodyTexts = new ArrayStack();
  105. /**
  106. * Stack whose elements are List objects, each containing a list of
  107. * Rule objects as returned from Rules.getMatch(). As each xml element
  108. * in the input is entered, the matching rules are pushed onto this
  109. * stack. After the end tag is reached, the matches are popped again.
  110. * The depth of is stack is therefore exactly the same as the current
  111. * "nesting" level of the input xml.
  112. *
  113. * @since 1.6
  114. */
  115. protected ArrayStack matches = new ArrayStack(10);
  116. /**
  117. * The class loader to use for instantiating application objects.
  118. * If not specified, the context class loader, or the class loader
  119. * used to load Digester itself, is used, based on the value of the
  120. * <code>useContextClassLoader</code> variable.
  121. */
  122. protected ClassLoader classLoader = null;
  123. /**
  124. * Has this Digester been configured yet.
  125. */
  126. protected boolean configured = false;
  127. /**
  128. * The EntityResolver used by the SAX parser. By default it use this class
  129. */
  130. protected EntityResolver entityResolver;
  131. /**
  132. * The URLs of entityValidator that have been registered, keyed by the public
  133. * identifier that corresponds.
  134. */
  135. protected HashMap entityValidator = new HashMap();
  136. /**
  137. * The application-supplied error handler that is notified when parsing
  138. * warnings, errors, or fatal errors occur.
  139. */
  140. protected ErrorHandler errorHandler = null;
  141. /**
  142. * The SAXParserFactory that is created the first time we need it.
  143. */
  144. protected SAXParserFactory factory = null;
  145. /**
  146. * @deprecated This is now managed by {@link ParserFeatureSetterFactory}
  147. */
  148. protected String JAXP_SCHEMA_LANGUAGE =
  149. "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
  150. /**
  151. * The Locator associated with our parser.
  152. */
  153. protected Locator locator = null;
  154. /**
  155. * The current match pattern for nested element processing.
  156. */
  157. protected String match = "";
  158. /**
  159. * Do we want a "namespace aware" parser.
  160. */
  161. protected boolean namespaceAware = false;
  162. /**
  163. * Registered namespaces we are currently processing. The key is the
  164. * namespace prefix that was declared in the document. The value is an
  165. * ArrayStack of the namespace URIs this prefix has been mapped to --
  166. * the top Stack element is the most current one. (This architecture
  167. * is required because documents can declare nested uses of the same
  168. * prefix for different Namespace URIs).
  169. */
  170. protected HashMap namespaces = new HashMap();
  171. /**
  172. * The parameters stack being utilized by CallMethodRule and
  173. * CallParamRule rules.
  174. */
  175. protected ArrayStack params = new ArrayStack();
  176. /**
  177. * The SAXParser we will use to parse the input stream.
  178. */
  179. protected SAXParser parser = null;
  180. /**
  181. * The public identifier of the DTD we are currently parsing under
  182. * (if any).
  183. */
  184. protected String publicId = null;
  185. /**
  186. * The XMLReader used to parse digester rules.
  187. */
  188. protected XMLReader reader = null;
  189. /**
  190. * The "root" element of the stack (in other words, the last object
  191. * that was popped.
  192. */
  193. protected Object root = null;
  194. /**
  195. * The <code>Rules</code> implementation containing our collection of
  196. * <code>Rule</code> instances and associated matching policy. If not
  197. * established before the first rule is added, a default implementation
  198. * will be provided.
  199. */
  200. protected Rules rules = null;
  201. /**
  202. * The XML schema language to use for validating an XML instance. By
  203. * default this value is set to <code>W3C_XML_SCHEMA</code>
  204. */
  205. protected String schemaLanguage = W3C_XML_SCHEMA;
  206. /**
  207. * The XML schema to use for validating an XML instance.
  208. */
  209. protected String schemaLocation = null;
  210. /**
  211. * The object stack being constructed.
  212. */
  213. protected ArrayStack stack = new ArrayStack();
  214. /**
  215. * Do we want to use the Context ClassLoader when loading classes
  216. * for instantiating new objects. Default is <code>false</code>.
  217. */
  218. protected boolean useContextClassLoader = false;
  219. /**
  220. * Do we want to use a validating parser.
  221. */
  222. protected boolean validating = false;
  223. /**
  224. * The Log to which most logging calls will be made.
  225. */
  226. protected Log log =
  227. LogFactory.getLog("org.apache.commons.digester.Digester");
  228. /**
  229. * The Log to which all SAX event related logging calls will be made.
  230. */
  231. protected Log saxLog =
  232. LogFactory.getLog("org.apache.commons.digester.Digester.sax");
  233. /**
  234. * The schema language supported. By default, we use this one.
  235. */
  236. protected static final String W3C_XML_SCHEMA =
  237. "http://www.w3.org/2001/XMLSchema";
  238. /**
  239. * An optional class that substitutes values in attributes and body text.
  240. * This may be null and so a null check is always required before use.
  241. */
  242. protected Substitutor substitutor;
  243. /** Stacks used for interrule communication, indexed by name String */
  244. private HashMap stacksByName = new HashMap();
  245. // ------------------------------------------------------------- Properties
  246. /**
  247. * Return the currently mapped namespace URI for the specified prefix,
  248. * if any; otherwise return <code>null</code>. These mappings come and
  249. * go dynamically as the document is parsed.
  250. *
  251. * @param prefix Prefix to look up
  252. */
  253. public String findNamespaceURI(String prefix) {
  254. ArrayStack stack = (ArrayStack) namespaces.get(prefix);
  255. if (stack == null) {
  256. return (null);
  257. }
  258. try {
  259. return ((String) stack.peek());
  260. } catch (EmptyStackException e) {
  261. return (null);
  262. }
  263. }
  264. /**
  265. * Return the class loader to be used for instantiating application objects
  266. * when required. This is determined based upon the following rules:
  267. * <ul>
  268. * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
  269. * <li>The thread context class loader, if it exists and the
  270. * <code>useContextClassLoader</code> property is set to true</li>
  271. * <li>The class loader used to load the Digester class itself.
  272. * </ul>
  273. */
  274. public ClassLoader getClassLoader() {
  275. if (this.classLoader != null) {
  276. return (this.classLoader);
  277. }
  278. if (this.useContextClassLoader) {
  279. ClassLoader classLoader =
  280. Thread.currentThread().getContextClassLoader();
  281. if (classLoader != null) {
  282. return (classLoader);
  283. }
  284. }
  285. return (this.getClass().getClassLoader());
  286. }
  287. /**
  288. * Set the class loader to be used for instantiating application objects
  289. * when required.
  290. *
  291. * @param classLoader The new class loader to use, or <code>null</code>
  292. * to revert to the standard rules
  293. */
  294. public void setClassLoader(ClassLoader classLoader) {
  295. this.classLoader = classLoader;
  296. }
  297. /**
  298. * Return the current depth of the element stack.
  299. */
  300. public int getCount() {
  301. return (stack.size());
  302. }
  303. /**
  304. * Return the name of the XML element that is currently being processed.
  305. */
  306. public String getCurrentElementName() {
  307. String elementName = match;
  308. int lastSlash = elementName.lastIndexOf('/');
  309. if (lastSlash >= 0) {
  310. elementName = elementName.substring(lastSlash + 1);
  311. }
  312. return (elementName);
  313. }
  314. /**
  315. * Return the debugging detail level of our currently enabled logger.
  316. *
  317. * @deprecated This method now always returns 0. Digester uses the apache
  318. * jakarta commons-logging library; see the documentation for that library
  319. * for more information.
  320. */
  321. public int getDebug() {
  322. return (0);
  323. }
  324. /**
  325. * Set the debugging detail level of our currently enabled logger.
  326. *
  327. * @param debug New debugging detail level (0=off, increasing integers
  328. * for more detail)
  329. *
  330. * @deprecated This method now has no effect at all. Digester uses
  331. * the apache jakarta comons-logging library; see the documentation
  332. * for that library for more information.
  333. */
  334. public void setDebug(int debug) {
  335. ; // No action is taken
  336. }
  337. /**
  338. * Return the error handler for this Digester.
  339. */
  340. public ErrorHandler getErrorHandler() {
  341. return (this.errorHandler);
  342. }
  343. /**
  344. * Set the error handler for this Digester.
  345. *
  346. * @param errorHandler The new error handler
  347. */
  348. public void setErrorHandler(ErrorHandler errorHandler) {
  349. this.errorHandler = errorHandler;
  350. }
  351. /**
  352. * Return the SAXParserFactory we will use, creating one if necessary.
  353. */
  354. public SAXParserFactory getFactory() {
  355. if (factory == null) {
  356. factory = SAXParserFactory.newInstance();
  357. factory.setNamespaceAware(namespaceAware);
  358. factory.setValidating(validating);
  359. }
  360. return (factory);
  361. }
  362. /**
  363. * Returns a flag indicating whether the requested feature is supported
  364. * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
  365. * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
  366. * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
  367. * for information about the standard SAX2 feature flags.
  368. *
  369. * @param feature Name of the feature to inquire about
  370. *
  371. * @exception ParserConfigurationException if a parser configuration error
  372. * occurs
  373. * @exception SAXNotRecognizedException if the property name is
  374. * not recognized
  375. * @exception SAXNotSupportedException if the property name is
  376. * recognized but not supported
  377. */
  378. public boolean getFeature(String feature)
  379. throws ParserConfigurationException, SAXNotRecognizedException,
  380. SAXNotSupportedException {
  381. return (getFactory().getFeature(feature));
  382. }
  383. /**
  384. * Sets a flag indicating whether the requested feature is supported
  385. * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
  386. * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
  387. * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
  388. * for information about the standard SAX2 feature flags. In order to be
  389. * effective, this method must be called <strong>before</strong> the
  390. * <code>getParser()</code> method is called for the first time, either
  391. * directly or indirectly.
  392. *
  393. * @param feature Name of the feature to set the status for
  394. * @param value The new value for this feature
  395. *
  396. * @exception ParserConfigurationException if a parser configuration error
  397. * occurs
  398. * @exception SAXNotRecognizedException if the property name is
  399. * not recognized
  400. * @exception SAXNotSupportedException if the property name is
  401. * recognized but not supported
  402. */
  403. public void setFeature(String feature, boolean value)
  404. throws ParserConfigurationException, SAXNotRecognizedException,
  405. SAXNotSupportedException {
  406. getFactory().setFeature(feature, value);
  407. }
  408. /**
  409. * Return the current Logger associated with this instance of the Digester
  410. */
  411. public Log getLogger() {
  412. return log;
  413. }
  414. /**
  415. * Set the current logger for this Digester.
  416. */
  417. public void setLogger(Log log) {
  418. this.log = log;
  419. }
  420. /**
  421. * Gets the logger used for logging SAX-related information.
  422. * <strong>Note</strong> the output is finely grained.
  423. *
  424. * @since 1.6
  425. */
  426. public Log getSAXLogger() {
  427. return saxLog;
  428. }
  429. /**
  430. * Sets the logger used for logging SAX-related information.
  431. * <strong>Note</strong> the output is finely grained.
  432. * @param saxLog Log, not null
  433. *
  434. * @since 1.6
  435. */
  436. public void setSAXLogger(Log saxLog) {
  437. this.saxLog = saxLog;
  438. }
  439. /**
  440. * Return the current rule match path
  441. */
  442. public String getMatch() {
  443. return match;
  444. }
  445. /**
  446. * Return the "namespace aware" flag for parsers we create.
  447. */
  448. public boolean getNamespaceAware() {
  449. return (this.namespaceAware);
  450. }
  451. /**
  452. * Set the "namespace aware" flag for parsers we create.
  453. *
  454. * @param namespaceAware The new "namespace aware" flag
  455. */
  456. public void setNamespaceAware(boolean namespaceAware) {
  457. this.namespaceAware = namespaceAware;
  458. }
  459. /**
  460. * Set the publid id of the current file being parse.
  461. * @param publicId the DTD/Schema public's id.
  462. */
  463. public void setPublicId(String publicId){
  464. this.publicId = publicId;
  465. }
  466. /**
  467. * Return the public identifier of the DTD we are currently
  468. * parsing under, if any.
  469. */
  470. public String getPublicId() {
  471. return (this.publicId);
  472. }
  473. /**
  474. * Return the namespace URI that will be applied to all subsequently
  475. * added <code>Rule</code> objects.
  476. */
  477. public String getRuleNamespaceURI() {
  478. return (getRules().getNamespaceURI());
  479. }
  480. /**
  481. * Set the namespace URI that will be applied to all subsequently
  482. * added <code>Rule</code> objects.
  483. *
  484. * @param ruleNamespaceURI Namespace URI that must match on all
  485. * subsequently added rules, or <code>null</code> for matching
  486. * regardless of the current namespace URI
  487. */
  488. public void setRuleNamespaceURI(String ruleNamespaceURI) {
  489. getRules().setNamespaceURI(ruleNamespaceURI);
  490. }
  491. /**
  492. * Return the SAXParser we will use to parse the input stream. If there
  493. * is a problem creating the parser, return <code>null</code>.
  494. */
  495. public SAXParser getParser() {
  496. // Return the parser we already created (if any)
  497. if (parser != null) {
  498. return (parser);
  499. }
  500. // Create a new parser
  501. try {
  502. if (validating) {
  503. Properties properties = new Properties();
  504. properties.put("SAXParserFactory", getFactory());
  505. if (schemaLocation != null) {
  506. properties.put("schemaLocation", schemaLocation);
  507. properties.put("schemaLanguage", schemaLanguage);
  508. }
  509. parser = ParserFeatureSetterFactory.newSAXParser(properties); } else {
  510. parser = getFactory().newSAXParser();
  511. }
  512. } catch (Exception e) {
  513. log.error("Digester.getParser: ", e);
  514. return (null);
  515. }
  516. return (parser);
  517. }
  518. /**
  519. * Return the current value of the specified property for the underlying
  520. * <code>XMLReader</code> implementation.
  521. * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
  522. * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
  523. * for information about the standard SAX2 properties.
  524. *
  525. * @param property Property name to be retrieved
  526. *
  527. * @exception SAXNotRecognizedException if the property name is
  528. * not recognized
  529. * @exception SAXNotSupportedException if the property name is
  530. * recognized but not supported
  531. */
  532. public Object getProperty(String property)
  533. throws SAXNotRecognizedException, SAXNotSupportedException {
  534. return (getParser().getProperty(property));
  535. }
  536. /**
  537. * Set the current value of the specified property for the underlying
  538. * <code>XMLReader</code> implementation.
  539. * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
  540. * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
  541. * for information about the standard SAX2 properties.
  542. *
  543. * @param property Property name to be set
  544. * @param value Property value to be set
  545. *
  546. * @exception SAXNotRecognizedException if the property name is
  547. * not recognized
  548. * @exception SAXNotSupportedException if the property name is
  549. * recognized but not supported
  550. */
  551. public void setProperty(String property, Object value)
  552. throws SAXNotRecognizedException, SAXNotSupportedException {
  553. getParser().setProperty(property, value);
  554. }
  555. /**
  556. * By setting the reader in the constructor, you can bypass JAXP and
  557. * be able to use digester in Weblogic 6.0.
  558. *
  559. * @deprecated Use getXMLReader() instead, which can throw a
  560. * SAXException if the reader cannot be instantiated
  561. */
  562. public XMLReader getReader() {
  563. try {
  564. return (getXMLReader());
  565. } catch (SAXException e) {
  566. log.error("Cannot get XMLReader", e);
  567. return (null);
  568. }
  569. }
  570. /**
  571. * Return the <code>Rules</code> implementation object containing our
  572. * rules collection and associated matching policy. If none has been
  573. * established, a default implementation will be created and returned.
  574. */
  575. public Rules getRules() {
  576. if (this.rules == null) {
  577. this.rules = new RulesBase();
  578. this.rules.setDigester(this);
  579. }
  580. return (this.rules);
  581. }
  582. /**
  583. * Set the <code>Rules</code> implementation object containing our
  584. * rules collection and associated matching policy.
  585. *
  586. * @param rules New Rules implementation
  587. */
  588. public void setRules(Rules rules) {
  589. this.rules = rules;
  590. this.rules.setDigester(this);
  591. }
  592. /**
  593. * Return the XML Schema URI used for validating an XML instance.
  594. */
  595. public String getSchema() {
  596. return (this.schemaLocation);
  597. }
  598. /**
  599. * Set the XML Schema URI used for validating a XML Instance.
  600. *
  601. * @param schemaLocation a URI to the schema.
  602. */
  603. public void setSchema(String schemaLocation){
  604. this.schemaLocation = schemaLocation;
  605. }
  606. /**
  607. * Return the XML Schema language used when parsing.
  608. */
  609. public String getSchemaLanguage() {
  610. return (this.schemaLanguage);
  611. }
  612. /**
  613. * Set the XML Schema language used when parsing. By default, we use W3C.
  614. *
  615. * @param schemaLanguage a URI to the schema language.
  616. */
  617. public void setSchemaLanguage(String schemaLanguage){
  618. this.schemaLanguage = schemaLanguage;
  619. }
  620. /**
  621. * Return the boolean as to whether the context classloader should be used.
  622. */
  623. public boolean getUseContextClassLoader() {
  624. return useContextClassLoader;
  625. }
  626. /**
  627. * Determine whether to use the Context ClassLoader (the one found by
  628. * calling <code>Thread.currentThread().getContextClassLoader()</code>)
  629. * to resolve/load classes that are defined in various rules. If not
  630. * using Context ClassLoader, then the class-loading defaults to
  631. * using the calling-class' ClassLoader.
  632. *
  633. * @param use determines whether to use Context ClassLoader.
  634. */
  635. public void setUseContextClassLoader(boolean use) {
  636. useContextClassLoader = use;
  637. }
  638. /**
  639. * Return the validating parser flag.
  640. */
  641. public boolean getValidating() {
  642. return (this.validating);
  643. }
  644. /**
  645. * Set the validating parser flag. This must be called before
  646. * <code>parse()</code> is called the first time.
  647. *
  648. * @param validating The new validating parser flag.
  649. */
  650. public void setValidating(boolean validating) {
  651. this.validating = validating;
  652. }
  653. /**
  654. * Return the XMLReader to be used for parsing the input document.
  655. *
  656. * FIX ME: there is a bug in JAXP/XERCES that prevent the use of a
  657. * parser that contains a schema with a DTD.
  658. * @exception SAXException if no XMLReader can be instantiated
  659. */
  660. public XMLReader getXMLReader() throws SAXException {
  661. if (reader == null){
  662. reader = getParser().getXMLReader();
  663. }
  664. reader.setDTDHandler(this);
  665. reader.setContentHandler(this);
  666. if (entityResolver == null){
  667. reader.setEntityResolver(this);
  668. } else {
  669. reader.setEntityResolver(entityResolver);
  670. }
  671. reader.setErrorHandler(this);
  672. return reader;
  673. }
  674. /**
  675. * Gets the <code>Substitutor</code> used to convert attributes and body text.
  676. * @return Substitutor, null if not substitutions are to be performed.
  677. */
  678. public Substitutor getSubstitutor() {
  679. return substitutor;
  680. }
  681. /**
  682. * Sets the <code>Substitutor</code> to be used to convert attributes and body text.
  683. * @param substitutor the Substitutor to be used to convert attributes and body text
  684. * or null if not substitution of these values is to be performed.
  685. */
  686. public void setSubstitutor(Substitutor substitutor) {
  687. this.substitutor = substitutor;
  688. }
  689. // ------------------------------------------------- ContentHandler Methods
  690. /**
  691. * Process notification of character data received from the body of
  692. * an XML element.
  693. *
  694. * @param buffer The characters from the XML document
  695. * @param start Starting offset into the buffer
  696. * @param length Number of characters from the buffer
  697. *
  698. * @exception SAXException if a parsing error is to be reported
  699. */
  700. public void characters(char buffer[], int start, int length)
  701. throws SAXException {
  702. if (saxLog.isDebugEnabled()) {
  703. saxLog.debug("characters(" + new String(buffer, start, length) + ")");
  704. }
  705. bodyText.append(buffer, start, length);
  706. }
  707. /**
  708. * Process notification of the end of the document being reached.
  709. *
  710. * @exception SAXException if a parsing error is to be reported
  711. */
  712. public void endDocument() throws SAXException {
  713. if (saxLog.isDebugEnabled()) {
  714. if (getCount() > 1) {
  715. saxLog.debug("endDocument(): " + getCount() +
  716. " elements left");
  717. } else {
  718. saxLog.debug("endDocument()");
  719. }
  720. }
  721. while (getCount() > 1) {
  722. pop();
  723. }
  724. // Fire "finish" events for all defined rules
  725. Iterator rules = getRules().rules().iterator();
  726. while (rules.hasNext()) {
  727. Rule rule = (Rule) rules.next();
  728. try {
  729. rule.finish();
  730. } catch (Exception e) {
  731. log.error("Finish event threw exception", e);
  732. throw createSAXException(e);
  733. } catch (Error e) {
  734. log.error("Finish event threw error", e);
  735. throw e;
  736. }
  737. }
  738. // Perform final cleanup
  739. clear();
  740. }
  741. /**
  742. * Process notification of the end of an XML element being reached.
  743. *
  744. * @param namespaceURI - The Namespace URI, or the empty string if the
  745. * element has no Namespace URI or if Namespace processing is not
  746. * being performed.
  747. * @param localName - The local name (without prefix), or the empty
  748. * string if Namespace processing is not being performed.
  749. * @param qName - The qualified XML 1.0 name (with prefix), or the
  750. * empty string if qualified names are not available.
  751. * @exception SAXException if a parsing error is to be reported
  752. */
  753. public void endElement(String namespaceURI, String localName,
  754. String qName) throws SAXException {
  755. boolean debug = log.isDebugEnabled();
  756. if (debug) {
  757. if (saxLog.isDebugEnabled()) {
  758. saxLog.debug("endElement(" + namespaceURI + "," + localName +
  759. "," + qName + ")");
  760. }
  761. log.debug(" match='" + match + "'");
  762. log.debug(" bodyText='" + bodyText + "'");
  763. }
  764. // the actual element name is either in localName or qName, depending
  765. // on whether the parser is namespace aware
  766. String name = localName;
  767. if ((name == null) || (name.length() < 1)) {
  768. name = qName;
  769. }
  770. // Fire "body" events for all relevant rules
  771. List rules = (List) matches.pop();
  772. if ((rules != null) && (rules.size() > 0)) {
  773. String bodyText = this.bodyText.toString();
  774. Substitutor substitutor = getSubstitutor();
  775. if (substitutor!= null) {
  776. bodyText = substitutor.substitute(bodyText);
  777. }
  778. for (int i = 0; i < rules.size(); i++) {
  779. try {
  780. Rule rule = (Rule) rules.get(i);
  781. if (debug) {
  782. log.debug(" Fire body() for " + rule);
  783. }
  784. rule.body(namespaceURI, name, bodyText);
  785. } catch (Exception e) {
  786. log.error("Body event threw exception", e);
  787. throw createSAXException(e);
  788. } catch (Error e) {
  789. log.error("Body event threw error", e);
  790. throw e;
  791. }
  792. }
  793. } else {
  794. if (debug) {
  795. log.debug(" No rules found matching '" + match + "'.");
  796. }
  797. }
  798. // Recover the body text from the surrounding element
  799. bodyText = (StringBuffer) bodyTexts.pop();
  800. if (debug) {
  801. log.debug(" Popping body text '" + bodyText.toString() + "'");
  802. }
  803. // Fire "end" events for all relevant rules in reverse order
  804. if (rules != null) {
  805. for (int i = 0; i < rules.size(); i++) {
  806. int j = (rules.size() - i) - 1;
  807. try {
  808. Rule rule = (Rule) rules.get(j);
  809. if (debug) {
  810. log.debug(" Fire end() for " + rule);
  811. }
  812. rule.end(namespaceURI, name);
  813. } catch (Exception e) {
  814. log.error("End event threw exception", e);
  815. throw createSAXException(e);
  816. } catch (Error e) {
  817. log.error("End event threw error", e);
  818. throw e;
  819. }
  820. }
  821. }
  822. // Recover the previous match expression
  823. int slash = match.lastIndexOf('/');
  824. if (slash >= 0) {
  825. match = match.substring(0, slash);
  826. } else {
  827. match = "";
  828. }
  829. }
  830. /**
  831. * Process notification that a namespace prefix is going out of scope.
  832. *
  833. * @param prefix Prefix that is going out of scope
  834. *
  835. * @exception SAXException if a parsing error is to be reported
  836. */
  837. public void endPrefixMapping(String prefix) throws SAXException {
  838. if (saxLog.isDebugEnabled()) {
  839. saxLog.debug("endPrefixMapping(" + prefix + ")");
  840. }
  841. // Deregister this prefix mapping
  842. ArrayStack stack = (ArrayStack) namespaces.get(prefix);
  843. if (stack == null) {
  844. return;
  845. }
  846. try {
  847. stack.pop();
  848. if (stack.empty())
  849. namespaces.remove(prefix);
  850. } catch (EmptyStackException e) {
  851. throw createSAXException("endPrefixMapping popped too many times");
  852. }
  853. }
  854. /**
  855. * Process notification of ignorable whitespace received from the body of
  856. * an XML element.
  857. *
  858. * @param buffer The characters from the XML document
  859. * @param start Starting offset into the buffer
  860. * @param len Number of characters from the buffer
  861. *
  862. * @exception SAXException if a parsing error is to be reported
  863. */
  864. public void ignorableWhitespace(char buffer[], int start, int len)
  865. throws SAXException {
  866. if (saxLog.isDebugEnabled()) {
  867. saxLog.debug("ignorableWhitespace(" +
  868. new String(buffer, start, len) + ")");
  869. }
  870. ; // No processing required
  871. }
  872. /**
  873. * Process notification of a processing instruction that was encountered.
  874. *
  875. * @param target The processing instruction target
  876. * @param data The processing instruction data (if any)
  877. *
  878. * @exception SAXException if a parsing error is to be reported
  879. */
  880. public void processingInstruction(String target, String data)
  881. throws SAXException {
  882. if (saxLog.isDebugEnabled()) {
  883. saxLog.debug("processingInstruction('" + target + "','" + data + "')");
  884. }
  885. ; // No processing is required
  886. }
  887. /**
  888. * Gets the document locator associated with our parser.
  889. *
  890. * @return the Locator supplied by the document parser
  891. */
  892. public Locator getDocumentLocator() {
  893. return locator;
  894. }
  895. /**
  896. * Sets the document locator associated with our parser.
  897. *
  898. * @param locator The new locator
  899. */
  900. public void setDocumentLocator(Locator locator) {
  901. if (saxLog.isDebugEnabled()) {
  902. saxLog.debug("setDocumentLocator(" + locator + ")");
  903. }
  904. this.locator = locator;
  905. }
  906. /**
  907. * Process notification of a skipped entity.
  908. *
  909. * @param name Name of the skipped entity
  910. *
  911. * @exception SAXException if a parsing error is to be reported
  912. */
  913. public void skippedEntity(String name) throws SAXException {
  914. if (saxLog.isDebugEnabled()) {
  915. saxLog.debug("skippedEntity(" + name + ")");
  916. }
  917. ; // No processing required
  918. }
  919. /**
  920. * Process notification of the beginning of the document being reached.
  921. *
  922. * @exception SAXException if a parsing error is to be reported
  923. */
  924. public void startDocument() throws SAXException {
  925. if (saxLog.isDebugEnabled()) {
  926. saxLog.debug("startDocument()");
  927. }
  928. // ensure that the digester is properly configured, as
  929. // the digester could be used as a SAX ContentHandler
  930. // rather than via the parse() methods.
  931. configure();
  932. }
  933. /**
  934. * Process notification of the start of an XML element being reached.
  935. *
  936. * @param namespaceURI The Namespace URI, or the empty string if the element
  937. * has no Namespace URI or if Namespace processing is not being performed.
  938. * @param localName The local name (without prefix), or the empty
  939. * string if Namespace processing is not being performed.
  940. * @param qName The qualified name (with prefix), or the empty
  941. * string if qualified names are not available.\
  942. * @param list The attributes attached to the element. If there are
  943. * no attributes, it shall be an empty Attributes object.
  944. * @exception SAXException if a parsing error is to be reported
  945. */
  946. public void startElement(String namespaceURI, String localName,
  947. String qName, Attributes list)
  948. throws SAXException {
  949. boolean debug = log.isDebugEnabled();
  950. if (saxLog.isDebugEnabled()) {
  951. saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
  952. qName + ")");
  953. }
  954. // Save the body text accumulated for our surrounding element
  955. bodyTexts.push(bodyText);
  956. if (debug) {
  957. log.debug(" Pushing body text '" + bodyText.toString() + "'");
  958. }
  959. bodyText = new StringBuffer();
  960. // the actual element name is either in localName or qName, depending
  961. // on whether the parser is namespace aware
  962. String name = localName;
  963. if ((name == null) || (name.length() < 1)) {
  964. name = qName;
  965. }
  966. // Compute the current matching rule
  967. StringBuffer sb = new StringBuffer(match);
  968. if (match.length() > 0) {
  969. sb.append('/');
  970. }
  971. sb.append(name);
  972. match = sb.toString();
  973. if (debug) {
  974. log.debug(" New match='" + match + "'");
  975. }
  976. // Fire "begin" events for all relevant rules
  977. List rules = getRules().match(namespaceURI, match);
  978. matches.push(rules);
  979. if ((rules != null) && (rules.size() > 0)) {
  980. Substitutor substitutor = getSubstitutor();
  981. if (substitutor!= null) {
  982. list = substitutor.substitute(list);
  983. }
  984. for (int i = 0; i < rules.size(); i++) {
  985. try {
  986. Rule rule = (Rule) rules.get(i);
  987. if (debug) {
  988. log.debug(" Fire begin() for " + rule);
  989. }
  990. rule.begin(namespaceURI, name, list);
  991. } catch (Exception e) {
  992. log.error("Begin event threw exception", e);
  993. throw createSAXException(e);
  994. } catch (Error e) {
  995. log.error("Begin event threw error", e);
  996. throw e;
  997. }
  998. }
  999. } else {
  1000. if (debug) {
  1001. log.debug(" No rules found matching '" + match + "'.");
  1002. }
  1003. }
  1004. }
  1005. /**
  1006. * Process notification that a namespace prefix is coming in to scope.
  1007. *
  1008. * @param prefix Prefix that is being declared
  1009. * @param namespaceURI Corresponding namespace URI being mapped to
  1010. *
  1011. * @exception SAXException if a parsing error is to be reported
  1012. */
  1013. public void startPrefixMapping(String prefix, String namespaceURI)
  1014. throws SAXException {
  1015. if (saxLog.isDebugEnabled()) {
  1016. saxLog.debug("startPrefixMapping(" + prefix + "," + namespaceURI + ")");
  1017. }
  1018. // Register this prefix mapping
  1019. ArrayStack stack = (ArrayStack) namespaces.get(prefix);
  1020. if (stack == null) {
  1021. stack = new ArrayStack();
  1022. namespaces.put(prefix, stack);
  1023. }
  1024. stack.push(namespaceURI);
  1025. }
  1026. // ----------------------------------------------------- DTDHandler Methods
  1027. /**
  1028. * Receive notification of a notation declaration event.
  1029. *
  1030. * @param name The notation name
  1031. * @param publicId The public identifier (if any)
  1032. * @param systemId The system identifier (if any)
  1033. */
  1034. public void notationDecl(String name, String publicId, String systemId) {
  1035. if (saxLog.isDebugEnabled()) {
  1036. saxLog.debug("notationDecl(" + name + "," + publicId + "," +
  1037. systemId + ")");
  1038. }
  1039. }
  1040. /**
  1041. * Receive notification of an unparsed entity declaration event.
  1042. *
  1043. * @param name The unparsed entity name
  1044. * @param publicId The public identifier (if any)
  1045. * @param systemId The system identifier (if any)
  1046. * @param notation The name of the associated notation
  1047. */
  1048. public void unparsedEntityDecl(String name, String publicId,
  1049. String systemId, String notation) {
  1050. if (saxLog.isDebugEnabled()) {
  1051. saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," +
  1052. systemId + "," + notation + ")");
  1053. }
  1054. }
  1055. // ----------------------------------------------- EntityResolver Methods
  1056. /**
  1057. * Set the <code>EntityResolver</code> used by SAX when resolving
  1058. * public id and system id.
  1059. * This must be called before the first call to <code>parse()</code>.
  1060. * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
  1061. */
  1062. public void setEntityResolver(EntityResolver entityResolver){
  1063. this.entityResolver = entityResolver;
  1064. }
  1065. /**
  1066. * Return the Entity Resolver used by the SAX parser.
  1067. * @return Return the Entity Resolver used by the SAX parser.
  1068. */
  1069. public EntityResolver getEntityResolver(){
  1070. return entityResolver;
  1071. }
  1072. /**
  1073. * Resolve the requested external entity.
  1074. *
  1075. * @param publicId The public identifier of the entity being referenced
  1076. * @param systemId The system identifier of the entity being referenced
  1077. *
  1078. * @exception SAXException if a parsing exception occurs
  1079. *
  1080. */
  1081. public InputSource resolveEntity(String publicId, String systemId)
  1082. throws SAXException {
  1083. if (saxLog.isDebugEnabled()) {
  1084. saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')");
  1085. }
  1086. if (publicId != null)
  1087. this.publicId = publicId;
  1088. // Has this system identifier been registered?
  1089. String entityURL = null;
  1090. if (publicId != null) {
  1091. entityURL = (String) entityValidator.get(publicId);
  1092. }
  1093. // Redirect the schema location to a local destination
  1094. if (schemaLocation != null && entityURL == null && systemId != null){
  1095. entityURL = (String)entityValidator.get(systemId);
  1096. }
  1097. if (entityURL == null) {
  1098. if (systemId == null) {
  1099. // cannot resolve
  1100. if (log.isDebugEnabled()) {
  1101. log.debug(" Cannot resolve entity: '" + entityURL + "'");
  1102. }
  1103. return (null);
  1104. } else {
  1105. // try to resolve using system ID
  1106. if (log.isDebugEnabled()) {
  1107. log.debug(" Trying to resolve using system ID '" + systemId + "'");
  1108. }
  1109. entityURL = systemId;
  1110. }
  1111. }
  1112. // Return an input source to our alternative URL
  1113. if (log.isDebugEnabled()) {
  1114. log.debug(" Resolving to alternate DTD '" + entityURL + "'");
  1115. }
  1116. try {
  1117. return (new InputSource(entityURL));
  1118. } catch (Exception e) {
  1119. throw createSAXException(e);
  1120. }
  1121. }
  1122. // ------------------------------------------------- ErrorHandler Methods
  1123. /**
  1124. * Forward notification of a parsing error to the application supplied
  1125. * error handler (if any).
  1126. *
  1127. * @param exception The error information
  1128. *
  1129. * @exception SAXException if a parsing exception occurs
  1130. */
  1131. public void error(SAXParseException exception) throws SAXException {
  1132. log.error("Parse Error at line " + exception.getLineNumber() +
  1133. " column " + exception.getColumnNumber() + ": " +
  1134. exception.getMessage(), exception);
  1135. if (errorHandler != null) {
  1136. errorHandler.error(exception);
  1137. }
  1138. }
  1139. /**
  1140. * Forward notification of a fatal parsing error to the application
  1141. * supplied error handler (if any).
  1142. *
  1143. * @param exception The fatal error information
  1144. *
  1145. * @exception SAXException if a parsing exception occurs
  1146. */
  1147. public void fatalError(SAXParseException exception) throws SAXException {
  1148. log.error("Parse Fatal Error at line " + exception.getLineNumber() +
  1149. " column " + exception.getColumnNumber() + ": " +
  1150. exception.getMessage(), exception);
  1151. if (errorHandler != null) {
  1152. errorHandler.fatalError(exception);
  1153. }
  1154. }
  1155. /**
  1156. * Forward notification of a parse warning to the application supplied
  1157. * error handler (if any).
  1158. *
  1159. * @param exception The warning information
  1160. *
  1161. * @exception SAXException if a parsing exception occurs
  1162. */
  1163. public void warning(SAXParseException exception) throws SAXException {
  1164. if (errorHandler != null) {
  1165. log.warn("Parse Warning Error at line " + exception.getLineNumber() +
  1166. " column " + exception.getColumnNumber() + ": " +
  1167. exception.getMessage(), exception);
  1168. errorHandler.warning(exception);
  1169. }
  1170. }
  1171. // ------------------------------------------------------- Public Methods
  1172. /**
  1173. * Log a message to our associated logger.
  1174. *
  1175. * @param message The message to be logged
  1176. * @deprecated Call getLogger() and use it's logging methods
  1177. */
  1178. public void log(String message) {
  1179. log.info(message);
  1180. }
  1181. /**
  1182. * Log a message and exception to our associated logger.
  1183. *
  1184. * @param message The message to be logged
  1185. * @deprecated Call getLogger() and use it's logging methods
  1186. */
  1187. public void log(String message, Throwable exception) {
  1188. log.error(message, exception);
  1189. }
  1190. /**
  1191. * Parse the content of the specified file using this Digester. Returns
  1192. * the root element from the object stack (if any).
  1193. *
  1194. * @param file File containing the XML data to be parsed
  1195. *
  1196. * @exception IOException if an input/output error occurs
  1197. * @exception SAXException if a parsing exception occurs
  1198. */
  1199. public Object parse(File file) throws IOException, SAXException {
  1200. configure();
  1201. InputSource input = new InputSource(new FileInputStream(file));
  1202. input.setSystemId("file://" + file.getAbsolutePath());
  1203. getXMLReader().parse(input);
  1204. return (root);
  1205. }
  1206. /**
  1207. * Parse the content of the specified input source using this Digester.
  1208. * Returns the root element from the object stack (if any).
  1209. *
  1210. * @param input Input source containing the XML data to be parsed
  1211. *
  1212. * @exception IOException if an input/output error occurs
  1213. * @exception SAXException if a parsing exception occurs
  1214. */
  1215. public Object parse(InputSource input) throws IOException, SAXException {
  1216. configure();
  1217. getXMLReader().parse(input);
  1218. return (root);
  1219. }
  1220. /**
  1221. * Parse the content of the specified input stream using this Digester.
  1222. * Returns the root element from the object stack (if any).
  1223. *
  1224. * @param input Input stream containing the XML data to be parsed
  1225. *
  1226. * @exception IOException if an input/output error occurs
  1227. * @exception SAXException if a parsing exception occurs
  1228. */
  1229. public Object parse(InputStream input) throws IOException, SAXException {
  1230. configure();
  1231. InputSource is = new InputSource(input);
  1232. getXMLReader().parse(is);
  1233. return (root);
  1234. }
  1235. /**
  1236. * Parse the content of the specified reader using this Digester.
  1237. * Returns the root element from the object stack (if any).
  1238. *
  1239. * @param reader Reader containing the XML data to be parsed
  1240. *
  1241. * @exception IOException if an input/output error occurs
  1242. * @exception SAXException if a parsing exception occurs
  1243. */
  1244. public Object parse(Reader reader) throws IOException, SAXException {
  1245. configure();
  1246. InputSource is = new InputSource(reader);
  1247. getXMLReader().parse(is);
  1248. return (root);
  1249. }
  1250. /**
  1251. * Parse the content of the specified URI using this Digester.
  1252. * Returns the root element from the object stack (if any).
  1253. *
  1254. * @param uri URI containing the XML data to be parsed
  1255. *
  1256. * @exception IOException if an input/output error occurs
  1257. * @exception SAXException if a parsing exception occurs
  1258. */
  1259. public Object parse(String uri) throws IOException, SAXException {
  1260. configure();
  1261. InputSource is = new InputSource(uri);
  1262. getXMLReader().parse(is);
  1263. return (root);
  1264. }
  1265. /**
  1266. * <p>Register the specified DTD URL for the specified public identifier.
  1267. * This must be called before the first call to <code>parse()</code>.
  1268. * </p><p>
  1269. * <code>Digester</code> contains an internal <code>EntityResolver</code>
  1270. * implementation. This maps <code>PUBLICID</code>'s to URLs
  1271. * (from which the resource will be loaded). A common use case for this
  1272. * method is to register local URLs (possibly computed at runtime by a
  1273. * classloader) for DTDs. This allows the performance advantage of using
  1274. * a local version without having to ensure every <code>SYSTEM</code>
  1275. * URI on every processed xml document is local. This implementation provides
  1276. * only basic functionality. If more sophisticated features are required,
  1277. * using {@link #setEntityResolver} to set a custom resolver is recommended.
  1278. * </p><p>
  1279. * <strong>Note:</strong> This method will have no effect when a custom
  1280. * <code>EntityResolver</code> has been set. (Setting a custom
  1281. * <code>EntityResolver</code> overrides the internal implementation.)
  1282. * </p>
  1283. * @param publicId Public identifier of the DTD to be resolved
  1284. * @param entityURL The URL to use for reading this DTD
  1285. */
  1286. public void register(String publicId, String entityURL) {
  1287. if (log.isDebugEnabled()) {
  1288. log.debug("register('" + publicId + "', '" + entityURL + "'");
  1289. }
  1290. entityValidator.put(publicId, entityURL);
  1291. }
  1292. // --------------------------------------------------------- Rule Methods
  1293. /**
  1294. * <p>Register a new Rule matching the specified pattern.
  1295. * This method sets the <code>Digester</code> property on the rule.</p>
  1296. *
  1297. * @param pattern Element matching pattern
  1298. * @param rule Rule to be registered
  1299. */
  1300. public void addRule(String pattern, Rule rule) {
  1301. rule.setDigester(this);
  1302. getRules().add(pattern, rule);
  1303. }
  1304. /**
  1305. * R