/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
  */
 
 import java.io.*;
 import java.util.*;
 
 import javax.xml.transform.*;
 import javax.xml.transform.sax.*;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
 import org.xml.sax.*;
 import org.xml.sax.helpers.AttributesImpl;
 
 /**
  * Utility class for xml/sax handling.
  * It provides support for "older" sax implementations (like the default one shipped with JDK 1.4.2)
  * which have bugs in the namespace handling.
  */
 public class IOUtils {
 
     /** The transformer factory. */
     private static final SAXTransformerFactory FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance();
 
     /** The URI for xml namespaces */
     private static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
 
     /**
      * Parse a file and send the sax events to the content handler.
      * @param file
      * @param handler
      * @throws IOException
      * @throws TransformerException
      */
     public static final void parse(File file, ContentHandler handler)
     throws IOException, TransformerException {
         final Transformer transformer = FACTORY.newTransformer();
         transformer.transform(new StreamSource(new FileReader(file)),
                 new SAXResult(handler));
     }
 
     public static ContentHandler getSerializer(File file)
     throws IOException, TransformerException {
         final FileWriter writer = new FileWriter(file);
 
         final TransformerHandler transformerHandler = FACTORY.newTransformerHandler();
         final Transformer transformer = transformerHandler.getTransformer();
 
         final Properties format = new Properties();
         format.put(OutputKeys.METHOD, "xml");
         format.put(OutputKeys.OMIT_XML_DECLARATION, "no");
         format.put(OutputKeys.ENCODING, "UTF-8");
         format.put(OutputKeys.INDENT, "yes");
         transformer.setOutputProperties(format);
 
         transformerHandler.setResult(new StreamResult(writer));
 
         try {
             if ( needsNamespacesAsAttributes(format) ) {
                 return new NamespaceAsAttributes(transformerHandler);
             }
         } catch (SAXException se) {
             throw new TransformerException("Unable to detect of namespace support for sax works properly.", se);
         }
         return transformerHandler;
     }
 
     /**
      * Checks if the used Trax implementation correctly handles namespaces set using
      * <code>startPrefixMapping()</code>, but wants them also as 'xmlns:' attributes.
      * <p>
      * The check consists in sending SAX events representing a minimal namespaced document
      * with namespaces defined only with calls to <code>startPrefixMapping</code> (no
      * xmlns:xxx attributes) and check if they are present in the resulting text.
      */
     protected static boolean needsNamespacesAsAttributes(Properties format)
     throws TransformerException, SAXException {
         // Serialize a minimal document to check how namespaces are handled.
         final StringWriter writer = new StringWriter();
 
         final String uri = "namespaceuri";
         final String prefix = "nsp";
         final String check = "xmlns:" + prefix + "='" + uri + "'";
 
         final TransformerHandler handler = FACTORY.newTransformerHandler();
 
         handler.getTransformer().setOutputProperties(format);
         handler.setResult(new StreamResult(writer));
 
         // Output a single element
         handler.startDocument();
         handler.startPrefixMapping(prefix, uri);
         handler.startElement(uri, "element", "element", new AttributesImpl());
         handler.endElement(uri, "element", "element");
         handler.endPrefixMapping(prefix);
         handler.endDocument();
 
         final String text = writer.toString();
 
         // Check if the namespace is there (replace " by ' to be sure of what we search in)
         boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1);
 
         return needsIt;
     }
 
     /**
      * A pipe that ensures that all namespace prefixes are also present as
      * 'xmlns:' attributes. This used to circumvent Xalan's serialization behaviour
      * which is to ignore namespaces if they're not present as 'xmlns:xxx' attributes.
      */
     public static class NamespaceAsAttributes implements ContentHandler {
 
         /** The wrapped content handler. */
         private final ContentHandler contentHandler;
 
         /**
          * The prefixes of startPrefixMapping() declarations for the coming element.
          */
         private List prefixList = new ArrayList();
 
         /**
          * The URIs of startPrefixMapping() declarations for the coming element.
          */
         private List uriList = new ArrayList();
 
         /**
          * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan
          * serializer.
          */
         private Map uriToPrefixMap = new HashMap();
         private Map prefixToUriMap = new HashMap();
 
         /**
          * True if there has been some startPrefixMapping() for the coming element.
          */
         private boolean hasMappings = false;
 
         public NamespaceAsAttributes(ContentHandler ch) {
             this.contentHandler = ch;
         }
 
         public void startDocument() throws SAXException {
             // Cleanup
             this.uriToPrefixMap.clear();
             this.prefixToUriMap.clear();
             clearMappings();
             this.contentHandler.startDocument();
         }
 
         /**
          * Track mappings to be able to add <code>xmlns:</code> attributes
          * in <code>startElement()</code>.
          */
         public void startPrefixMapping(String prefix, String uri) throws SAXException {
             // Store the mappings to reconstitute xmlns:attributes
             // except prefixes starting with "xml": these are reserved
             // VG: (uri != null) fixes NPE in startElement
             if (uri != null && !prefix.startsWith("xml")) {
                 this.hasMappings = true;
                 this.prefixList.add(prefix);
                 this.uriList.add(uri);
 
                 // append the prefix colon now, in order to save concatenations later, but
                 // only for non-empty prefixes.
                 if (prefix.length() > 0) {
                     this.uriToPrefixMap.put(uri, prefix + ":");
                 } else {
                     this.uriToPrefixMap.put(uri, prefix);
                 }
 
                 this.prefixToUriMap.put(prefix, uri);
             }
             this.contentHandler.startPrefixMapping(prefix, uri);
         }
 
         /**
          * Ensure all namespace declarations are present as <code>xmlns:</code> attributes
          * and add those needed before calling superclass. This is a workaround for a Xalan bug
          * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code>
          * ignores <code>start/endPrefixMapping()</code>.
          */
         public void startElement(String eltUri, String eltLocalName, String eltQName, Attributes attrs)
                 throws SAXException {
 
             // try to restore the qName. The map already contains the colon
             if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
             }
             if (this.hasMappings) {
                 // Add xmlns* attributes where needed
 
                 // New Attributes if we have to add some.
                 AttributesImpl newAttrs = null;
 
                 int mappingCount = this.prefixList.size();
                 int attrCount = attrs.getLength();
 
                 for (int mapping = 0; mapping < mappingCount; mapping++) {
 
                     // Build infos for this namespace
                     String uri = (String) this.uriList.get(mapping);
                     String prefix = (String) this.prefixList.get(mapping);
                     String qName = prefix.length() == 0 ? "xmlns" : ("xmlns:" + prefix);
 
                     // Search for the corresponding xmlns* attribute
                     boolean found = false;
                     for (int attr = 0; attr < attrCount; attr++) {
                         if (qName.equals(attrs.getQName(attr))) {
                             // Check if mapping and attribute URI match
                             if (!uri.equals(attrs.getValue(attr))) {
                                 throw new SAXException("URI in prefix mapping and attribute do not match");
                             }
                             found = true;
                             break;
                         }
                     }
 
                     if (!found) {
                         // Need to add this namespace
                         if (newAttrs == null) {
                             // Need to test if attrs is empty or we go into an infinite loop...
                             // Well know SAX bug which I spent 3 hours to remind of :-(
                             if (attrCount == 0) {
                                 newAttrs = new AttributesImpl();
                             } else {
                                 newAttrs = new AttributesImpl(attrs);
                             }
                         }
 
                         if (prefix.length() == 0) {
                             newAttrs.addAttribute(XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri);
                         } else {
                             newAttrs.addAttribute(XML_NAMESPACE_URI, prefix, qName, "CDATA", uri);
                         }
                     }
                 } // end for mapping
 
                 // Cleanup for the next element
                 clearMappings();
 
                 // Start element with new attributes, if any
                 this.contentHandler.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs);
             } else {
                 // Normal job
                 this.contentHandler.startElement(eltUri, eltLocalName, eltQName, attrs);
             }
         }
 
 
         /**
          * Receive notification of the end of an element.
          * Try to restore the element qName.
          */
         public void endElement(String eltUri, String eltLocalName, String eltQName) throws SAXException {
             // try to restore the qName. The map already contains the colon
             if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
             }
             this.contentHandler.endElement(eltUri, eltLocalName, eltQName);
         }
 
         /**
          * End the scope of a prefix-URI mapping:
          * remove entry from mapping tables.
          */
         public void endPrefixMapping(String prefix) throws SAXException {
             // remove mappings for xalan-bug-workaround.
             // Unfortunately, we're not passed the uri, but the prefix here,
             // so we need to maintain maps in both directions.
             if (this.prefixToUriMap.containsKey(prefix)) {
                 this.uriToPrefixMap.remove(this.prefixToUriMap.get(prefix));
                 this.prefixToUriMap.remove(prefix);
             }
 
             if (hasMappings) {
                 // most of the time, start/endPrefixMapping calls have an element event between them,
                 // which will clear the hasMapping flag and so this code will only be executed in the
                 // rather rare occasion when there are start/endPrefixMapping calls with no element
                 // event in between. If we wouldn't remove the items from the prefixList and uriList here,
                 // the namespace would be incorrectly declared on the next&n   
sources68 the directory containing the programming examples, source code and programming guide online
Sunday, 31 July 2011
Utility class for xml/sax handling
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment