Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/derby/iapi/types/XML.java


1   /*
2   
3      Derby - Class org.apache.derby.iapi.types.XML
4   
5      Copyright 2005 The Apache Software Foundation or its licensors, as applicable.
6   
7      Licensed under the Apache License, Version 2.0 (the "License");
8      you may not use this file except in compliance with the License.
9      You may obtain a copy of the License at
10  
11        http://www.apache.org/licenses/LICENSE-2.0
12  
13     Unless required by applicable law or agreed to in writing, software
14     distributed under the License is distributed on an "AS IS" BASIS,
15     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16     See the License for the specific language governing permissions and
17     limitations under the License.
18  
19   */
20  
21  package org.apache.derby.iapi.types;
22  
23  import org.apache.derby.iapi.error.StandardException;
24  
25  import org.apache.derby.iapi.services.cache.ClassSize;
26  import org.apache.derby.iapi.services.io.ArrayInputStream;
27  import org.apache.derby.iapi.services.io.StoredFormatIds;
28  import org.apache.derby.iapi.services.io.StreamStorable;
29  import org.apache.derby.iapi.services.sanity.SanityManager;
30  
31  import org.apache.derby.iapi.types.DataValueDescriptor;
32  import org.apache.derby.iapi.types.StringDataValue;
33  import org.apache.derby.iapi.types.BooleanDataValue;
34  
35  import org.apache.derby.iapi.reference.SQLState;
36  
37  import java.sql.ResultSet;
38  import java.sql.SQLException;
39  import java.sql.Types;
40  
41  import java.io.InputStream;
42  import java.io.IOException;
43  import java.io.ObjectOutput;
44  import java.io.ObjectInput;
45  import java.io.StringReader;
46  
47  import org.xml.sax.ErrorHandler;
48  import org.xml.sax.XMLReader;
49  import org.xml.sax.SAXException;
50  import org.xml.sax.SAXParseException;
51  import org.xml.sax.InputSource;
52  
53  import org.xml.sax.helpers.DefaultHandler;
54  import org.xml.sax.helpers.XMLReaderFactory;
55  
56  import javax.xml.transform.Templates;
57  import javax.xml.transform.TransformerFactory;
58  
59  import javax.xml.transform.sax.SAXResult;
60  import javax.xml.transform.sax.TemplatesHandler;
61  import javax.xml.transform.sax.TransformerHandler;
62  
63  // Note that even though the following has a Xalan
64  // package name, it IS part of the JDK 1.4 API, and
65  // thus we can compile it without having Xalan in
66  // our classpath.
67  import org.apache.xalan.processor.TransformerFactoryImpl;
68  
69  /**
70   * This type implements the XMLDataValue interface and thus is
71   * the type on which all XML related operations are executed.
72   *
73   * The first and simplest XML store implementation is a UTF-8
74   * based one--all XML data is stored on disk as a UTF-8 string,
75   * just like the other Derby string types.  In order to make
76   * it possible for smarter XML implementations to exist in
77   * the future, this class always writes an "XML implementation
78   * id" to disk before writing the rest of its data.  When
79   * reading the data, the impl id is read first and serves
80   * as an indicator of how the rest of the data should be
81   * read.
82   *
83   * So long as there's only one implementation (UTF-8)
84   * the impl id can be ignored; but when smarter implementations
85   * are written, the impl id will be the key to figuring out
86   * how an XML value should be read, written, and processed.
87   */
88  public class XML
89      extends DataType implements XMLDataValue, StreamStorable
90  {
91      // Id for this implementation.  Should be unique
92      // across all XML type implementations.
93      protected static final short UTF8_IMPL_ID = 0;
94  
95      // Parser class to use for parsing XML.  We use the
96      // Xerces parser, so (for now) we require that Xerces
97      // be in the user's classpath.  Note that we load
98      // the Xerces class dynamically (using the class 
99      // name) so that Derby will build even if Xerces
100     // isn't in the build environment; i.e. Xerces is
101     // only required if XML is actually going to be used
102     // at runtime; it's not required for a successful
103     // build nor for non-XML database use.
104     protected static final String XML_PARSER_CLASS =
105         "org.apache.xerces.parsers.SAXParser";
106 
107     // Guess at how much memory this type will take.
108     private static final int BASE_MEMORY_USAGE =
109         ClassSize.estimateBaseFromCatalog(XML.class);
110 
111     // The actual XML data in this implementation is just a simple
112     // string, so this class really just wraps a SQLChar and
113     // defers most calls to the corresponding calls on that
114     // SQLChar.  Note that, even though a SQLChar is the
115     // underlying implementation, an XML value is nonetheless
116     // NOT considered comparable nor compatible with any of
117     // Derby string types.
118     private SQLChar xmlStringValue;
119 
120     // An XML reader for reading and parsing SAX events.
121     protected XMLReader saxReader;
122 
123     // XSLT objects used when performing an XSLT query, which
124     // is the query mechanism for this UTF8-based implementation.
125     private static final String XPATH_PLACEHOLDER = "XPATH_PLACEHOLDER";
126     private static final String QUERY_MATCH_STRING = "MATCH";
127     private static String xsltStylesheet;
128     private XMLReader xsltReader;
129     private TransformerFactoryImpl saxTFactory;
130 
131     /**
132      * Default constructor.
133      */
134     public XML()
135     {
136         xmlStringValue = null;
137     }
138 
139     /**
140      * Private constructor used for the getClone() method.
141      * Takes a SQLChar and clones it.
142      * @param val A SQLChar instance to clone and use for
143      *  this XML value.
144      */
145     private XML(SQLChar val)
146     {
147         xmlStringValue = (val == null ? null : (SQLChar)val.getClone());
148     }
149 
150     /* ****
151      * DataValueDescriptor interface.
152      * */
153 
154     /**
155      * @see DataValueDescriptor#getClone
156      */
157     public DataValueDescriptor getClone()
158     {
159         return new XML(xmlStringValue);
160     }
161 
162     /**
163      * @see DataValueDescriptor#getNewNull
164      */
165     public DataValueDescriptor getNewNull()
166     {
167         return new XML();
168     }
169 
170     /**
171      * @see DataValueDescriptor#getTypeName
172      */
173     public String getTypeName()
174     {
175         return TypeId.XML_NAME;
176     }
177 
178     /**
179      * @see DataValueDescriptor#typePrecedence
180      */
181     public int typePrecedence()
182     {
183         return TypeId.XML_PRECEDENCE;
184     }
185 
186     /**
187      * @see DataValueDescriptor#getString
188      */
189     public String getString() throws StandardException
190     {
191         return (xmlStringValue == null) ? null : xmlStringValue.getString();
192     }
193 
194     /**
195      * @see DataValueDescriptor#getLength
196      */
197     public int    getLength() throws StandardException
198     {
199         return ((xmlStringValue == null) ? 0 : xmlStringValue.getLength());
200     }
201 
202     /** 
203      * @see DataValueDescriptor#estimateMemoryUsage
204      */
205     public int estimateMemoryUsage()
206     {
207         int sz = BASE_MEMORY_USAGE;
208         if (xmlStringValue != null)
209             sz += xmlStringValue.estimateMemoryUsage();
210         return sz;
211     }
212 
213     /**
214      * @see DataValueDescriptor#readExternalFromArray
215      */
216     public void readExternalFromArray(ArrayInputStream in)
217         throws IOException
218     {
219         if (xmlStringValue == null)
220             xmlStringValue = new SQLChar();
221 
222         // Read the XML implementation id.  Right now there's
223         // only one implementation (UTF-8 based), so we don't
224         // use this value.  But if better implementations come
225         // up in the future, we'll have to use this impl id to
226         // figure out how to read the data.
227         in.readShort();
228 
229         // Now just read the XML data as UTF-8.
230         xmlStringValue.readExternalFromArray(in);
231     }
232 
233     /**
234      * @see DataValueDescriptor#setFrom
235      */
236     protected void setFrom(DataValueDescriptor theValue)
237         throws StandardException
238     {
239         if (xmlStringValue == null)
240             xmlStringValue = new SQLChar();
241         xmlStringValue.setValue(theValue.getString());
242     }
243 
244     /** 
245      * @see DataValueDescriptor#setValueFromResultSet 
246      */
247     public final void setValueFromResultSet(
248         ResultSet resultSet, int colNumber, boolean isNullable)
249         throws SQLException
250     {
251         if (xmlStringValue == null)
252             xmlStringValue = new SQLChar();
253         xmlStringValue.setValue(resultSet.getString(colNumber));
254     }
255 
256     /**
257      * Compare two XML DataValueDescriptors.  NOTE: This method
258      * should only be used by the database store for the purpose of
259      * index positioning--comparisons of XML type are not allowed
260      * from the language side of things.  That said, all store
261      * wants to do is order the NULLs, so we don't actually
262      * have to do a full comparison.  Just return an order
263      * value based on whether or not this XML value and the
264      * other XML value are null.  As mentioned in the "compare"
265      * method of DataValueDescriptor, nulls are considered
266      * equal to other nulls and less than all other values.
267      *
268      * An example of when this method might be used is if the
269      * user executed a query like:
270      *
271      * select i from x_table where x_col is not null
272      *
273      * @see DataValueDescriptor#compare
274      */
275     public int compare(DataValueDescriptor other)
276         throws StandardException
277     {
278         if (SanityManager.DEBUG) {
279             SanityManager.ASSERT(other instanceof XMLDataValue,
280                 "Store should NOT have tried to compare an XML value " +
281                 "with a non-XML value.");
282         }
283 
284         if (isNull()) {
285             if (other.isNull())
286             // both null, so call them 'equal'.
287                 return 0;
288             // This XML is 'less than' the other.
289             return -1;
290         }
291 
292         if (other.isNull())
293         // This XML is 'greater than' the other.
294             return 1;
295 
296         // Two non-null values: we shouldn't ever get here,
297         // since that would necessitate a comparsion of XML
298         // values, which isn't allowed.
299         if (SanityManager.DEBUG) {
300             SanityManager.THROWASSERT(
301                 "Store tried to compare two non-null XML values, " +
302                 "which isn't allowed.");
303         }
304         return 0;
305     }
306 
307     /* ****
308      * Storable interface, implies Externalizable, TypedFormat
309      */
310 
311     /**
312      * @see TypedFormat#getTypeFormatId
313      *
314      * From the engine's perspective, all XML implementations share
315      * the same format id.
316      */
317     public int getTypeFormatId() {
318         return StoredFormatIds.XML_ID;
319     }
320 
321     /**
322      * @see Storable#isNull
323      */
324     public boolean isNull()
325     {
326         return ((xmlStringValue == null) || xmlStringValue.isNull());
327     }
328 
329     /**
330      * @see Storable#restoreToNull
331      */
332     public void restoreToNull()
333     {
334         if (xmlStringValue != null)
335             xmlStringValue.restoreToNull();
336     }
337 
338     /**
339      * Read an XML value from an input stream.
340      * @param in The stream from which we're reading.
341      */
342     public void readExternal(ObjectInput in) throws IOException
343     {
344         if (xmlStringValue == null)
345             xmlStringValue = new SQLChar();
346 
347         // Read the XML implementation id.  Right now there's
348         // only one implementation (UTF-8 based), so we don't
349         // use this value.  But if better implementations come
350         // up in the future, we'll have to use this impl id to
351         // figure out how to read the data.
352         in.readShort();
353 
354         // Now just read the XML data as UTF-8.
355         xmlStringValue.readExternal(in);
356     }
357 
358     /**
359      * Write an XML value. 
360      * @param out The stream to which we're writing.
361      */
362     public void writeExternal(ObjectOutput out) throws IOException
363     {
364         // never called when value is null
365         if (SanityManager.DEBUG)
366             SanityManager.ASSERT(!isNull());
367 
368         // Write out the XML store impl id.
369         out.writeShort(UTF8_IMPL_ID);
370 
371         // Now write out the data.
372         xmlStringValue.writeExternal(out);
373     }
374 
375     /* ****
376      * StreamStorable interface
377      * */
378 
379     /**
380      * @see StreamStorable#returnStream
381      */
382     public InputStream returnStream()
383     {
384         return
385             (xmlStringValue == null) ? null : xmlStringValue.returnStream();
386     }
387 
388     /**
389      * @see StreamStorable#setStream
390      */
391     public void setStream(InputStream newStream)
392     {
393         if (xmlStringValue == null)
394             xmlStringValue = new SQLChar();
395 
396         // The stream that we receive is for an XML data value,
397         // which means it has an XML implementation id stored
398         // at the front (we put it there when we wrote it out).
399         // If we leave that there we'll get a failure when
400         // our underlying SQLChar tries to read from the
401         // stream, because the extra impl id will throw
402         // off the UTF format.  So we need to read in (and
403         // ignore) the impl id before using the stream.
404         try {
405             // 2 bytes equal a short, which is what an impl id is.
406             newStream.read();
407             newStream.read();
408         } catch (Exception e) {
409             if (SanityManager.DEBUG)
410                 SanityManager.THROWASSERT("Failed to read impl id" +
411                     "bytes in setStream.");
412         }
413 
414         // Now go ahead and use the stream.
415         xmlStringValue.setStream(newStream);
416     }
417 
418     /**
419      * @see StreamStorable#loadStream
420      */
421     public void loadStream() throws StandardException
422     {
423         getString();
424     }
425 
426     /* ****
427      * XMLDataValue interface.
428      * */
429 
430     /**
431      * Method to parse an XML string and, if it's valid,
432      * store the _parsed_ version for subsequent use.
433      * @param text The string value to check.
434      * @param preserveWS Whether or not to preserve
435      *  ignorable whitespace.
436      * @return  If 'text' constitutes a valid XML document,
437      *  it has been stored in this XML value and nothing
438      *  is returned; otherwise, an exception is thrown.
439      * @exception StandardException Thrown on parse error.
440      */
441     public void parseAndLoadXML(String text, boolean preserveWS)
442         throws StandardException
443     {
444         try {
445 
446             if (preserveWS) {
447             // We're just going to use the text exactly as it
448             // is, so we just need to see if it parses. 
449                 loadSAXReader();
450                 saxReader.parse(
451                     new InputSource(new StringReader(text)));
452             }
453             else {
454             // We don't support this yet, so we shouldn't
455             // get here.
456                 if (SanityManager.DEBUG)
457                     SanityManager.THROWASSERT("Tried to STRIP whitespace " +
458                         "but we shouldn't have made it this far");
459             }
460 
461         } catch (Exception xe) {
462         // The text isn't a valid XML document.  Throw a StandardException
463         // with the parse exception nested in it.
464             throw StandardException.newException(
465                 SQLState.LANG_NOT_AN_XML_DOCUMENT, xe);
466         }
467 
468         // If we get here, the text is valid XML so go ahead
469         // and load/store it.
470         if (xmlStringValue == null)
471             xmlStringValue = new SQLChar();
472         xmlStringValue.setValue(text);
473         return;
474     }
475 
476     /**
477      * The SQL/XML XMLSerialize operator.
478      * Converts this XML value into a string with a user-specified
479      * type, and returns that string via the received StringDataValue
480      * (if the received StringDataValue is non-null; else a new
481      * StringDataValue is returned).
482      * @param result The result of a previous call to this method,
483      *    null if not called yet.
484      * @param targetType The string type to which we want to serialize.
485      * @param targetWidth The width of the target type.
486      * @return A serialized (to string) version of this XML object,
487      *  in the form of a StringDataValue object.
488      * @exception StandardException    Thrown on error
489      */
490     public StringDataValue XMLSerialize(StringDataValue result,
491         int targetType, int targetWidth) throws StandardException
492     {
493         if (result == null) {
494             switch (targetType)
495             {
496                 case Types.CHAR:        result = new SQLChar(); break;
497                 case Types.VARCHAR:     result = new SQLVarchar(); break;
498                 case Types.LONGVARCHAR: result = new SQLLongvarchar(); break;
499                 case Types.CLOB:        result = new SQLClob(); break;
500                 default:
501                 // Shouldn't ever get here, as this check was performed
502                 // at bind time.
503 
504                     if (SanityManager.DEBUG) {
505                         SanityManager.THROWASSERT(
506                             "Should NOT have made it to XMLSerialize " +
507                             "with a non-string target type.");
508                     }
509                     return null;
510             }
511         }
512 
513         // Else we're reusing a StringDataValue.  We only reuse
514         // the result if we're executing the _same_ XMLSERIALIZE
515         // call on multiple rows.  That means that all rows
516         // must have the same result type (targetType) and thus
517         // we know that the StringDataValue already has the
518         // correct type.  So we're set.
519 
520         if (this.isNull()) {
521         // Attempts to serialize a null XML value lead to a null
522         // result (SQL/XML[2003] section 10.13).
523             result.setToNull();
524             return result;
525         }
526 
527         // Get the XML value as a string.  For this UTF-8 impl,
528         // we already have it as a string, so just use that.
529         result.setValue(xmlStringValue.getString());
530 
531         // Seems wrong to trunc an XML document, as it then becomes non-
532         // well-formed and thus useless.  So we throw an error (that's
533         // what the "true" in the next line says).
534         result.setWidth(targetWidth, 0, true);
535         return result;
536     }
537 
538     /**
539      * The SQL/XML XMLExists operator.
540      * Takes an XML query expression (as a string) and an XML
541      * value and checks if at least one node in the XML
542      * value matches the query expression.  NOTE: For now,
543      * the query expression must be XPath only (XQuery not
544      * supported).
545      * @param xExpr The query expression, as a string.
546      * @param xml The XML value being queried.
547      * @return True if the received query expression matches at
548      *  least one node in the received XML value; unknown if
549      *  either the query expression or the xml value is null;
550      *  false otherwise.
551      * @exception StandardException Thrown on error
552      */
553     public BooleanDataValue XMLExists(StringDataValue xExpr,
554         XMLDataValue xml) throws StandardException
555     {
556         if ((xExpr == null) || xExpr.isNull())
557         // If the query is null, we assume unknown.
558             return SQLBoolean.unknownTruthValue();
559 
560         if ((xml == null) || xml.isNull())
561         // Then per SQL/XML spec 8.4, we return UNKNOWN.
562             return SQLBoolean.unknownTruthValue();
563 
564         return new SQLBoolean(xml.exists(xExpr.getString()));
565     }
566 
567     /**
568      * Helper method for XMLExists.
569      * See if the received XPath expression returns at least
570      * one node when evaluated against _this_ XML value.
571      * @param xExpr The XPath expression.
572      * @return True if at least one node in this XML value
573      *  matches the received xExpr; false otherwise.
574      */
575     public boolean exists(String xExpr) throws StandardException
576     {
577         // NOTE: At some point we'll probably need to implement some
578         // some kind of query cache so that we don't have to recompile
579         // the same query over and over for every single XML row
580         // in a table.  That's what we do right now...
581 
582         try {
583 
584             xExpr = replaceDoubleQuotes(xExpr);
585             loadXSLTObjects();
586 
587             // Take our simple stylesheet and plug in the query.
588             int pos = xsltStylesheet.indexOf(XPATH_PLACEHOLDER);
589             StringBuffer stylesheet = new StringBuffer(xsltStylesheet);
590             stylesheet.replace(pos, pos + XPATH_PLACEHOLDER.length(), xExpr);
591 
592             // Create a Templates ContentHandler to handle parsing of the 
593             // stylesheet.
594             TemplatesHandler templatesHandler = 
595                 saxTFactory.newTemplatesHandler();
596             xsltReader.setContentHandler(templatesHandler);
597     
598             // Now parse the generic stylesheet we created.
599             xsltReader.parse(
600                 new InputSource(new StringReader(stylesheet.toString())));
601 
602             // Get the Templates object (generated during the parsing of
603             // the stylesheet) from the TemplatesHandler.
604             Templates compiledQuery = templatesHandler.getTemplates();
605 
606             // Create a Transformer ContentHandler to handle parsing of 
607             // the XML Source.  
608             TransformerHandler transformerHandler 
609                 = saxTFactory.newTransformerHandler(compiledQuery);
610 
611             // Reset the XMLReader's ContentHandler to the TransformerHandler.
612             xsltReader.setContentHandler(transformerHandler);
613 
614             // Create an ExistsHandler.  When the XSLT transformation
615             // occurs, a period (".") will be thrown to this handler
616             // (via a SAX 'characters' event) for every matching
617             // node that XSLT finds.  This is how we know if a
618             // match was found.
619             ExistsHandler eH = new ExistsHandler();
620             transformerHandler.setResult(new SAXResult(eH));
621 
622             // This call to "parse" is what does the query, because we
623             // passed in an XSLT handler with the compiled query above.
624             try {
625                 xsltReader.parse(
626                     new InputSource(new StringReader(getString())));
627             } catch (Throwable th) {
628                 if (th.getMessage().indexOf(
629                     "SAXException: " + QUERY_MATCH_STRING) == -1)
630                 { // then this isn't the exception that means we have
631                   // a match; so re-throw it.
632                     throw new Exception(th.getMessage());
633                 }
634             }
635 
636             // Did we have any matches?
637             return eH.exists();
638 
639         } catch (Exception xe) {
640         // We don't expect to get here.  Turn it into a
641         // StandardException, then throw it.
642             throw StandardException.newException(
643                 SQLState.LANG_UNEXPECTED_XML_EXCEPTION, xe);
644         }
645     }
646 
647     /* ****
648      * Helper classes and methods.
649      * */
650 
651     /**
652      * Load an XMLReader for SAX events that can be used
653      * for parsing XML data.
654      *
655      * This method is currently only used for XMLPARSE, and
656      * the SQL/XML[2003] spec says that XMLPARSE should NOT
657      * perform validation -- Seciont 6.11:
658      *
659      *    "Perform a non-validating parse of a character string to
660      *    produce an XML value."
661      *
662      * Thus, we make sure to disable validation on the XMLReader
663      * loaded here.  At some point in the future we will probably
664      * want to add support for the XMLVALIDATE function--but until
665      * then, user is unable to validate the XML values s/he inserts.
666      *
667      * Note that, even with validation turned off, XMLPARSE
668      * _will_ still check the well-formedness of the values,
669      * and it _will_ still process DTDs to get default values,
670      * etc--but that's it; no validation errors will be thrown.
671      *
672      * For future reference: the features needed to perform
673      * validation (with Xerces) are:
674      *
675      * http://apache.org/xml/features/validation/schema
676      * http://apache.org/xml/features/validation/dynamic
677      */
678     protected void loadSAXReader() throws Exception
679     {
680         if (saxReader != null)
681         // already loaded.
682             return;
683 
684         // Get an instance of an XMLReader.
685         saxReader = XMLReaderFactory.createXMLReader(XML_PARSER_CLASS);
686 
687         // Turn off validation, since it's not allowed by
688         // SQL/XML[2003] spec.
689         saxReader.setFeature(
690             "http://xml.org/sax/features/validation", false);
691 
692         // Make the parser namespace aware.
693         saxReader.setFeature(
694             "http://xml.org/sax/features/namespaces", true);
695 
696         // We have to set the error handler in order to properly
697         // receive the parse errors.
698         saxReader.setErrorHandler(new XMLErrorHandler());
699     }
700 
701     /**
702      * Prepare for an XSLT query by loading the objects
703      * required for such a query.  We should only have
704      * to do this once per XML object.
705      */
706     private void loadXSLTObjects() throws SAXException
707     {
708         if (xsltReader != null)
709         // we already loaded everything.
710             return;
711 
712         // Instantiate a TransformerFactory.
713         TransformerFactory tFactory = TransformerFactory.newInstance();
714 
715         // Cast the TransformerFactory to SAXTransformerFactory.
716         saxTFactory = (TransformerFactoryImpl)tFactory;
717 
718         // Get an XML reader.
719         xsltReader = XMLReaderFactory.createXMLReader(XML_PARSER_CLASS);
720 
721         // Make the parser namespace aware.  Note that because we
722         // only support a small subset of SQL/XML, and because we
723         // only allow XPath (as opposed to XQuery) expressions,
724         // there is no way for a user to specify namespace
725         // bindings as part of the XMLEXISTS operator.  This means
726         // that in order to query for a node name, the user must
727         // use the XPath functions "name()" and "local-name()"
728         // in conjunction with XPath 1.0 'namespace' axis.  For
729         // example:
730         //
731         // To see if any elements exist that have a specific name
732         // with ANY namespace:
733         //     //child::*[local-name()="someName"]
734         //
735         // To see if any elements exist that have a specific name
736         // with NO namespace:
737         //     //child::*[name()="someName"]
738         //
739         // To see if any elements exist that have a specific name
740         // in a specific namespace:
741         //     //child::*[local-name()=''someName'' and
742         //        namespace::*[string()=''http://www.some.namespace'']]
743         //
744         xsltReader.setFeature(
745             "http://xml.org/sax/features/namespaces", true);
746 
747         // Create a very simple XSLT stylesheet.  This stylesheet
748         // will execute the XPath expression and, for every match,
749         // write a period (".") to the ExistsHandler (see the exists()
750         // method above).  Then, in order to see if at least one
751         // node matches, we just check to see if the ExistsHandler
752         // caught at least one 'characters' event.  If it did, then
753         // we know we had a match.
754         if (xsltStylesheet == null) {
755             StringBuffer sb = new StringBuffer();
756             sb.append("<xsl:stylesheet version=\"1.0\"\n");
757             sb.append("xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n");
758             sb.append(" <xsl:template match=\"/\">\n"); // Search whole doc...
759             sb.append("  <xsl:for-each select=\"");     // For every match...
760             sb.append(XPATH_PLACEHOLDER);               // using XPath expr...
761             sb.append("\">.</xsl:for-each>\n");         // Write a "."
762             sb.append(" </xsl:template>\n");
763             sb.append("</xsl:stylesheet>\n");
764             xsltStylesheet = sb.toString();
765         }
766     }
767 
768     /**
769      * Takes a string (which is an XPath query specified by
770      * the user) and replaces any double quotes with single
771      * quotes.  We have to do this because a double quote
772      * in the XSLT stylesheet (which is where the user's
773      * query ends up) will be parsed as a query terminator
774      * thus will cause XSLT execution errors.
775      * @param queryText Text in which we want to replace double
776      *  quotes.
777      * @return queryText with all double quotes replaced by
778      *  single quotes.
779      */
780     private String replaceDoubleQuotes(String queryText)
781     {
782         int pos = queryText.indexOf("\"");
783         if (pos == -1)
784         // nothing to do.
785             return queryText;
786 
787         StringBuffer sBuf = new StringBuffer(queryText);
788         while (pos >= 0) {
789             sBuf.replace(pos, pos+1, "'");
790             pos = queryText.indexOf("\"", pos+1);
791         }
792         return sBuf.toString();
793     }
794 
795     /*
796      ** The XMLErrorHandler class is just a generic implementation
797      ** of the ErrorHandler interface.  It allows us to catch
798      ** and process XML parsing errors in a graceful manner.
799      */
800     private class XMLErrorHandler implements ErrorHandler
801     {
802         public void error (SAXParseException exception)
803             throws SAXException
804         {
805             throw new SAXException (exception);
806         }
807 
808         public void fatalError (SAXParseException exception)
809             throws SAXException
810         {
811             throw new SAXException (exception);
812         }
813 
814         public void warning (SAXParseException exception)
815             throws SAXException
816         {
817             throw new SAXException (exception);
818         }
819     }
820 
821     /*
822      ** The ExistsHandler is what we pass to the XSLT processor
823      ** when we query.  The generic xsltStylesheet that we defined
824      ** above will throw a 'characters' event for every matching
825      ** node that is found by the XSLT transformation.  This
826      ** handler is the one that catches the event, and thus
827      ** it tells us whether or not we had a match.
828      */
829     private class ExistsHandler extends DefaultHandler
830     {
831         // Did we catch at least one 'characters' event?
832         private boolean atLeastOneMatch;
833 
834         public ExistsHandler() {
835             atLeastOneMatch = false;
836         }
837 
838         /*
839          * Catch a SAX 'characters' event, which tells us that
840          * we had at least one matching node.
841          */
842         public void characters(char[] ch, int start, int length)
843             throws SAXException
844         {
845             // If we get here, we had at least one matching node.
846             // Since that's all we need to know, we don't have
847             // to continue querying--we can stop the XSLT
848             // transformation now by throwing a SAX exception.
849             atLeastOneMatch = true;
850             throw new SAXException(QUERY_MATCH_STRING);
851         }
852 
853         /*
854          * Tell whether or not this handler caught a match.
855          */
856         public boolean exists()
857         {
858             return atLeastOneMatch;
859         }
860     }
861 }