OSDN Git Service

PR classpath/29362:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / xml / transform / TransformerImpl.java
1 /* TransformerImpl.java -- 
2    Copyright (C) 2004,2005,2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package gnu.xml.transform;
39
40 import java.io.BufferedOutputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.io.UnsupportedEncodingException;
45 import java.io.Writer;
46 import java.net.MalformedURLException;
47 import java.net.UnknownServiceException;
48 import java.net.URL;
49 import java.net.URLConnection;
50 import java.util.Collection;
51 import java.util.Iterator;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Properties;
55 import java.util.StringTokenizer;
56 import javax.xml.namespace.QName;
57 import javax.xml.transform.ErrorListener;
58 import javax.xml.transform.OutputKeys;
59 import javax.xml.transform.Result;
60 import javax.xml.transform.Source;
61 import javax.xml.transform.Transformer;
62 import javax.xml.transform.TransformerConfigurationException;
63 import javax.xml.transform.TransformerException;
64 import javax.xml.transform.URIResolver;
65 import javax.xml.transform.dom.DOMSource;
66 import javax.xml.transform.dom.DOMResult;
67 import javax.xml.transform.sax.SAXResult;
68 import javax.xml.transform.stream.StreamResult;
69 import org.w3c.dom.Document;
70 import org.w3c.dom.DocumentType;
71 import org.w3c.dom.DOMImplementation;
72 import org.w3c.dom.Node;
73 import org.w3c.dom.Text;
74 import org.xml.sax.ContentHandler;
75 import org.xml.sax.SAXException;
76 import org.xml.sax.ext.LexicalHandler;
77 import gnu.xml.dom.DomDoctype;
78 import gnu.xml.dom.DomDocument;
79 import gnu.xml.dom.ls.WriterOutputStream;
80
81 /**
82  * The transformation process for a given stylesheet.
83  *
84  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
85  */
86 class TransformerImpl
87   extends Transformer
88 {
89
90   final TransformerFactoryImpl factory;
91   final Stylesheet stylesheet;
92   URIResolver uriResolver;
93   ErrorListener errorListener;
94   Properties outputProperties;
95
96   TransformerImpl(TransformerFactoryImpl factory,
97                   Stylesheet stylesheet,
98                   Properties outputProperties)
99     throws TransformerConfigurationException
100   {
101     this.factory = factory;
102     uriResolver = factory.userResolver;
103     errorListener = factory.userListener;
104     this.stylesheet = stylesheet;
105     this.outputProperties = outputProperties;
106     if (stylesheet != null)
107       {
108         // Set up parameter context for this transformer
109         stylesheet.bindings.push(Bindings.PARAM);
110       }
111   }
112
113   public void transform(Source xmlSource, Result outputTarget)
114     throws TransformerException
115   {
116     // Get the source tree
117     DOMSource source;
118     synchronized (factory.resolver)
119       {
120         factory.resolver.setUserResolver(uriResolver);
121         factory.resolver.setUserListener(errorListener);
122         source = factory.resolver.resolveDOM(xmlSource, null, null);
123       }
124     Node context = source.getNode();
125     Document doc = (context instanceof Document) ? (Document) context :
126       context.getOwnerDocument();
127     if (doc instanceof DomDocument)
128       {
129         // Suppress mutation events
130         ((DomDocument) doc).setBuilding(true);
131       }
132     // Get the result tree
133     Node parent = null, nextSibling = null;
134     if (outputTarget instanceof DOMResult)
135       {
136         DOMResult dr = (DOMResult) outputTarget;
137         parent = dr.getNode();
138         nextSibling = dr.getNextSibling();
139
140         Document rdoc = (parent instanceof Document) ? (Document) parent :
141           parent.getOwnerDocument();
142         if (rdoc instanceof DomDocument)
143           {
144             // Suppress mutation events and allow multiple root elements
145             DomDocument drdoc = (DomDocument) rdoc;
146             drdoc.setBuilding(true);
147             drdoc.setCheckWellformedness(false);
148           }
149       }
150     boolean created = false;
151     // Transformation
152     if (stylesheet != null)
153       {
154         if (parent == null)
155           {
156             // Create a new document to hold the result
157             DomDocument resultDoc = new DomDocument();
158             resultDoc.setBuilding(true);
159             resultDoc.setCheckWellformedness(false);
160             parent = resultDoc;
161             created = true;
162           }
163         // Make a copy of the source node, and strip it
164         context = context.cloneNode(true);
165         strip(stylesheet, context);
166         // XSLT transformation
167         try
168           {
169             // Set output properties in the underlying stylesheet
170             ((TransformerOutputProperties) outputProperties).apply();
171             stylesheet.initTopLevelVariables(context);
172             TemplateNode t = stylesheet.getTemplate(null, context, false);
173             if (t != null)
174               {
175                 stylesheet.current = context;
176                 t.apply(stylesheet, null, context, 1, 1, parent, nextSibling);
177               }
178           }
179         catch (TransformerException e)
180           {
181             // Done transforming, reset document
182             if (doc instanceof DomDocument)
183               ((DomDocument) doc).setBuilding(false);
184             throw e;
185           }
186       }
187     else
188       {
189         // Identity transform
190         Node clone = context.cloneNode(true);
191         if (context.getNodeType() != Node.DOCUMENT_NODE)
192           {
193             Document resultDoc;
194             if (parent == null)
195               {
196                 // Create a new document to hold the result
197                 DomDocument rd = new DomDocument();
198                 rd.setBuilding(true);
199                 rd.setCheckWellformedness(false);
200                 parent = resultDoc = rd;
201                 created = true;
202               }
203             else
204               {
205                 resultDoc = (parent instanceof Document) ?
206                   (Document) parent :
207                   parent.getOwnerDocument();
208               }
209             Document sourceDoc = context.getOwnerDocument();
210             if (sourceDoc != resultDoc)
211               clone = resultDoc.adoptNode(clone);
212             if (nextSibling != null)
213               parent.insertBefore(clone, nextSibling);
214             else
215               parent.appendChild(clone);
216           }
217         else
218           {
219             // Cannot append document to another tree
220             parent = clone;
221             created = true;
222           }
223       }
224     String method = outputProperties.getProperty(OutputKeys.METHOD);
225     int outputMethod = "html".equals(method) ? Stylesheet.OUTPUT_HTML :
226       "text".equals(method) ? Stylesheet.OUTPUT_TEXT :
227       Stylesheet.OUTPUT_XML;
228     String encoding = outputProperties.getProperty(OutputKeys.ENCODING);
229     String publicId = outputProperties.getProperty(OutputKeys.DOCTYPE_PUBLIC);
230     String systemId = outputProperties.getProperty(OutputKeys.DOCTYPE_SYSTEM);
231     String version = outputProperties.getProperty(OutputKeys.VERSION);
232     boolean omitXmlDeclaration = 
233       "yes".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION));
234     boolean standalone = 
235       "yes".equals(outputProperties.getProperty(OutputKeys.STANDALONE));
236     String mediaType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
237     String cdataSectionElements =
238       outputProperties.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS);
239     boolean indent =
240       "yes".equals(outputProperties.getProperty(OutputKeys.INDENT));
241     if (created && parent instanceof DomDocument)
242       {
243         // Discover document element
244         DomDocument resultDoc = (DomDocument) parent;
245         Node root = resultDoc.getDocumentElement();
246         // Add doctype if specified
247         if (publicId != null || systemId != null)
248           {
249             if (root != null)
250               {
251                 // We must know the name of the root element to
252                 // create the document type
253                 DocumentType doctype = new DomDoctype(resultDoc,
254                                                       root.getNodeName(),
255                                                       publicId,
256                                                       systemId);
257                 resultDoc.insertBefore(doctype, root);
258               }
259           }
260         resultDoc.setBuilding(false);
261         resultDoc.setCheckWellformedness(true);
262       }
263     else if (publicId != null || systemId != null)
264       {
265         switch (parent.getNodeType())
266           {
267           case Node.DOCUMENT_NODE:
268           case Node.DOCUMENT_FRAGMENT_NODE:
269             Document resultDoc = (parent instanceof Document) ?
270               (Document) parent :
271               parent.getOwnerDocument();
272             DOMImplementation impl = resultDoc.getImplementation();
273             Node root = resultDoc.getDocumentElement();
274             if (root != null)
275               {
276                 DocumentType doctype =
277                   impl.createDocumentType(root.getNodeName(),
278                                           publicId,
279                                           systemId);
280                 resultDoc.insertBefore(doctype, root);
281               }
282           }
283       }
284     if (version != null)
285       parent.setUserData("version", version, stylesheet);
286     if (omitXmlDeclaration)
287       parent.setUserData("omit-xml-declaration", "yes", stylesheet);
288     if (standalone)
289       parent.setUserData("standalone", "yes", stylesheet);
290     if (mediaType != null)
291       parent.setUserData("media-type", mediaType, stylesheet);
292     if (cdataSectionElements != null)
293       {
294         List list = new LinkedList();
295         StringTokenizer st = new StringTokenizer(cdataSectionElements);
296         while (st.hasMoreTokens())
297           {
298             String name = st.nextToken();
299             String localName = name;
300             String uri = null;
301             String prefix = null;
302             int ci = name.indexOf(':');
303             if (ci != -1)
304               {
305                 // Use namespaces defined on xsl:output node to resolve
306                 // namespaces for QName
307                 prefix = name.substring(0, ci);
308                 localName = name.substring(ci + 1);
309                 uri = stylesheet.output.lookupNamespaceURI(prefix);
310               }
311             list.add(new QName(uri, localName, prefix));
312           }
313         if (!list.isEmpty())
314           {
315             Document resultDoc = (parent instanceof Document) ?
316               (Document) parent :
317               parent.getOwnerDocument();
318             convertCdataSectionElements(resultDoc, parent, list);
319           }
320       }
321     if (indent)
322       {
323         if (created && parent instanceof DomDocument)
324           {
325             DomDocument domDoc = (DomDocument) parent;
326             domDoc.setBuilding(true);
327             domDoc.setCheckWellformedness(false);
328           }
329         parent.normalize();
330         if (stylesheet != null)
331           strip(stylesheet, parent);
332         Document resultDoc = (parent instanceof Document) ?
333           (Document) parent :
334           parent.getOwnerDocument();
335         reindent(resultDoc, parent, 0);
336         if (created && parent instanceof DomDocument)
337           {
338             DomDocument domDoc = (DomDocument) parent;
339             domDoc.setBuilding(false);
340             domDoc.setCheckWellformedness(true);
341           }
342       }
343     // Render result to the target device
344     if (outputTarget instanceof DOMResult)
345       {
346         if (created)
347           {
348             DOMResult dr = (DOMResult) outputTarget;
349             dr.setNode(parent);
350             dr.setNextSibling(null);
351           }
352       }
353     else if (outputTarget instanceof StreamResult)
354       {
355         StreamResult sr = (StreamResult) outputTarget;
356         IOException ex = null;
357         try
358           {
359             writeStreamResult(parent, sr, outputMethod, encoding);
360           }
361         catch (UnsupportedEncodingException e)
362           {
363             try
364               {
365                 writeStreamResult(parent, sr, outputMethod, "UTF-8");
366               }
367             catch (IOException e2)
368               {
369                 ex = e2;
370               }
371           }
372         catch (IOException e)
373           {
374             ex = e;
375           }
376         if (ex != null)
377           {
378             if (errorListener != null)
379               errorListener.error(new TransformerException(ex));
380             else
381               ex.printStackTrace(System.err);
382           }
383       }
384     else if (outputTarget instanceof SAXResult)
385       {
386         SAXResult sr = (SAXResult) outputTarget;
387         try
388           {
389             ContentHandler ch = sr.getHandler();
390             LexicalHandler lh = sr.getLexicalHandler();
391             if (lh == null && ch instanceof LexicalHandler)
392               lh = (LexicalHandler) ch;
393             SAXSerializer serializer = new SAXSerializer();
394             serializer.serialize(parent, ch, lh);
395           }
396         catch (SAXException e)
397           {
398             if (errorListener != null)
399               errorListener.error(new TransformerException(e));
400             else
401               e.printStackTrace(System.err);
402           }
403       }
404   }
405
406   /**
407    * Strip whitespace from the source tree.
408    */
409   static boolean strip(Stylesheet stylesheet, Node node)
410     throws TransformerConfigurationException
411   {
412     short nt = node.getNodeType();
413     if (nt == Node.ENTITY_REFERENCE_NODE)
414       {
415         // Replace entity reference with its content
416         Node parent = node.getParentNode();
417         Node nextSibling = node.getNextSibling();
418         Node child = node.getFirstChild();
419         while (child != null)
420           {
421             Node next = child.getNextSibling();
422             node.removeChild(child);
423             if (nextSibling != null)
424               parent.insertBefore(child, nextSibling);
425             else
426               parent.appendChild(child);
427             child = next;
428           }
429         return true;
430       }
431     if (nt == Node.TEXT_NODE || nt == Node.CDATA_SECTION_NODE)
432       {
433         // Denormalize text into whitespace and non-whitespace nodes
434         String text = node.getNodeValue();
435         String[] tokens = tokenizeWhitespace(text);
436         if (tokens.length > 1)
437           {
438             node.setNodeValue(tokens[0]);
439             Node parent = node.getParentNode();
440             Node nextSibling = node.getNextSibling();
441             Document doc = node.getOwnerDocument();
442             for (int i = 1; i < tokens.length; i++)
443               {
444                 Node newChild = (nt == Node.CDATA_SECTION_NODE) ?
445                   doc.createCDATASection(tokens[i]) :
446                   doc.createTextNode(tokens[i]);
447                 if (nextSibling != null)
448                   parent.insertBefore(newChild, nextSibling);
449                 else
450                   parent.appendChild(newChild);
451               }
452           }
453         return !stylesheet.isPreserved((Text) node, true);
454       }
455     else
456       {
457         Node child = node.getFirstChild();
458         while (child != null)
459           {
460             boolean remove = strip(stylesheet, child);
461             Node next = child.getNextSibling();
462             if (remove)
463               node.removeChild(child);
464             child = next;
465           }
466       }
467     return false;
468   }
469
470   /**
471    * Tokenize the specified text into contiguous whitespace-only and
472    * non-whitespace chunks.
473    */
474   private static String[] tokenizeWhitespace(String text)
475   {
476     int len = text.length();
477     int start = 0, end = len - 1;
478     // Find index of text start
479     for (int i = 0; i < len; i++)
480       {
481         char c = text.charAt(i);
482         boolean whitespace = (c == ' ' || c == '\n' || c == '\t' || c == '\r');
483         if (whitespace)
484           start++;
485         else
486           break;
487       }
488     if (start == end) // all whitespace
489       return new String[] { text };
490     // Find index of text end
491     for (int i = end; i > start; i--)
492       {
493         char c = text.charAt(i);
494         boolean whitespace = (c == ' ' || c == '\n' || c == '\t' || c == '\r');
495         if (whitespace)
496           end--;
497         else
498           break;
499       }
500     if (start == 0 && end == len - 1) // all non-whitespace
501       return new String[] { text };
502     // whitespace, then text, then whitespace
503     String[] ret = (start > 0 && end < len - 1) ?
504       new String[3] : new String[2];
505     int i = 0;
506     if (start > 0)
507       ret[i++] = text.substring(0, start);
508     ret[i++] = text.substring(start, end + 1);
509     if (end < len - 1)
510       ret[i++] = text.substring(end + 1);
511     return ret;
512   }
513
514   /**
515    * Obtain a suitable output stream for writing the result to,
516    * and use the StreamSerializer to write the result tree to the stream.
517    */
518   void writeStreamResult(Node node, StreamResult sr, int outputMethod,
519                          String encoding)
520     throws IOException
521   {
522     OutputStream out = null;
523     boolean created = false;
524     try
525       {
526         out = sr.getOutputStream();
527         if (out == null)
528           {
529             Writer writer = sr.getWriter();
530             if (writer != null)
531               out = new WriterOutputStream(writer);
532           }
533         if (out == null)
534           {
535             String systemId = sr.getSystemId();
536             try
537               {
538                 URL url = new URL(systemId);
539                 URLConnection connection = url.openConnection();
540                 // We need to call setDoInput(false), because our
541                 // implementation of the file protocol allows writing
542                 // (unlike Sun), but it will fail with a FileNotFoundException
543                 // if we also open the connection for input and the output
544                 // file doesn't yet exist.
545                 connection.setDoInput(false);
546                 connection.setDoOutput(true);
547                 out = connection.getOutputStream();
548               }
549             catch (MalformedURLException e)
550               {
551                 out = new FileOutputStream(systemId);
552               }
553             catch (UnknownServiceException e)
554               {
555                 URL url = new URL(systemId);
556                 out = new FileOutputStream(url.getPath());
557               }
558             created = true;
559           }
560         out = new BufferedOutputStream(out);
561         StreamSerializer serializer =
562           new StreamSerializer(outputMethod, encoding, null);
563         if (stylesheet != null)
564           {
565             Collection celem = stylesheet.outputCdataSectionElements;
566             serializer.setCdataSectionElements(celem);
567           }
568         serializer.serialize(node, out);
569         out.flush();
570       }
571     finally
572       {
573         try
574           {
575             if (out != null && created)
576               out.close();
577           }
578         catch (IOException e)
579           {
580             if (errorListener != null)
581               {
582                 try
583                   {
584                     errorListener.error(new TransformerException(e));
585                   }
586                 catch (TransformerException e2)
587                   {
588                     e2.printStackTrace(System.err);
589                   }
590               }
591             else
592               e.printStackTrace(System.err);
593           }
594       }
595   }
596
597   void copyChildren(Document dstDoc, Node src, Node dst)
598   {
599     Node srcChild = src.getFirstChild();
600     while (srcChild != null)
601       {
602         Node dstChild = dstDoc.adoptNode(srcChild);
603         dst.appendChild(dstChild);
604         srcChild = srcChild.getNextSibling();
605       }
606   }
607
608   public void setParameter(String name, Object value)
609   {
610     if (stylesheet != null)
611       stylesheet.bindings.set(new QName(null, name), value, Bindings.PARAM);
612   }
613
614   public Object getParameter(String name)
615   {
616     if (stylesheet != null)
617       return stylesheet.bindings.get(new QName(null, name), null, 1, 1);
618     return null;
619   }
620
621   public void clearParameters()
622   {
623     if (stylesheet != null)
624       {
625         stylesheet.bindings.pop(Bindings.PARAM);
626         stylesheet.bindings.push(Bindings.PARAM);
627       }
628   }
629
630   public void setURIResolver(URIResolver resolver)
631   {
632     uriResolver = resolver;
633   }
634
635   public URIResolver getURIResolver()
636   {
637     return uriResolver;
638   }
639
640   public void setOutputProperties(Properties oformat)
641     throws IllegalArgumentException
642   {
643     if (oformat == null)
644       outputProperties.clear();
645     else
646       outputProperties.putAll(oformat);
647   }
648
649   public Properties getOutputProperties()
650   {
651     return (Properties) outputProperties.clone();
652   }
653
654   public void setOutputProperty(String name, String value)
655     throws IllegalArgumentException
656   {
657     outputProperties.put(name, value);
658   }
659
660   public String getOutputProperty(String name)
661     throws IllegalArgumentException
662   {
663     return outputProperties.getProperty(name);
664   }
665
666   public void setErrorListener(ErrorListener listener)
667   {
668     errorListener = listener;
669   }
670
671   public ErrorListener getErrorListener()
672   {
673     return errorListener;
674   }
675
676   static final String INDENT_WHITESPACE = "  ";
677
678   /*
679    * Apply indent formatting to the given tree.
680    */
681   void reindent(Document doc, Node node, int offset)
682   {
683     if (node.hasChildNodes())
684       {
685         boolean markupContent = false;
686         boolean textContent = false;
687         List children = new LinkedList();
688         Node ctx = node.getFirstChild();
689         while (ctx != null)
690           {
691             switch (ctx.getNodeType())
692               {
693               case Node.ELEMENT_NODE:
694               case Node.PROCESSING_INSTRUCTION_NODE:
695               case Node.DOCUMENT_TYPE_NODE:
696                 markupContent = true;
697                 break;
698               case Node.TEXT_NODE:
699               case Node.CDATA_SECTION_NODE:
700               case Node.ENTITY_REFERENCE_NODE:
701               case Node.COMMENT_NODE:
702                 textContent = true;
703                 break;
704               }
705             children.add(ctx);
706             ctx = ctx.getNextSibling();
707           }
708         if (markupContent)
709           {
710             if (textContent)
711               {
712                 // XXX handle mixed content differently?
713               }
714             int nodeType = node.getNodeType();
715             if (nodeType == Node.DOCUMENT_NODE)
716               {
717                 for (Iterator i = children.iterator(); i.hasNext(); )
718                   {
719                     ctx = (Node) i.next();
720                     reindent(doc, ctx, offset);
721                   }
722               }
723             else
724               {
725                 StringBuffer buf = new StringBuffer();
726                 buf.append('\n');
727                 for (int i = 0; i < offset + 1; i++)
728                   buf.append(INDENT_WHITESPACE);
729                 String ws = buf.toString();
730                 for (Iterator i = children.iterator(); i.hasNext(); )
731                   {
732                     ctx = (Node) i.next();
733                     node.insertBefore(doc.createTextNode(ws), ctx);
734                     reindent(doc, ctx, offset + 1);
735                   }
736                 buf = new StringBuffer();
737                 buf.append('\n');
738                 for (int i = 0; i < offset; i++)
739                   buf.append(INDENT_WHITESPACE);
740                 ws = buf.toString();
741                 node.appendChild(doc.createTextNode(ws));
742               }
743           }
744       }
745   }
746
747   /**
748    * Converts the text node children of any cdata-section-elements in the
749    * tree to CDATA section nodes.
750    */
751   void convertCdataSectionElements(Document doc, Node node, List list)
752   {
753     if (node.getNodeType() == Node.ELEMENT_NODE)
754       {
755         boolean match = false;
756         for (Iterator i = list.iterator(); i.hasNext(); )
757           {
758             QName qname = (QName) i.next();
759             if (match(qname, node))
760               {
761                 match = true;
762                 break;
763               }
764           }
765         if (match)
766           {
767             Node ctx = node.getFirstChild();
768             while (ctx != null)
769               {
770                 if (ctx.getNodeType() == Node.TEXT_NODE)
771                   {
772                     Node cdata = doc.createCDATASection(ctx.getNodeValue());
773                     node.replaceChild(cdata, ctx);
774                     ctx = cdata;
775                   }
776                 ctx = ctx.getNextSibling();
777               }
778           }
779       }
780     Node ctx = node.getFirstChild();
781     while (ctx != null)
782       {
783         if (ctx.hasChildNodes())
784           convertCdataSectionElements(doc, ctx, list);
785         ctx = ctx.getNextSibling();
786       }
787   }
788
789   boolean match(QName qname, Node node)
790   {
791     String ln1 = qname.getLocalPart();
792     String ln2 = node.getLocalName();
793     if (ln2 == null)
794       return ln1.equals(node.getNodeName());
795     else
796       {
797         String uri1 = qname.getNamespaceURI();
798         String uri2 = node.getNamespaceURI();
799         return (uri1.equals(uri2) && ln1.equals(ln2));
800       }
801   }
802
803 }