001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.util;
021    
022    import org.w3c.dom.Document;
023    import org.w3c.dom.Element;
024    import org.w3c.dom.Node;
025    import org.w3c.dom.NodeList;
026    import org.xml.sax.EntityResolver;
027    import org.xml.sax.InputSource;
028    import org.xml.sax.SAXException;
029    
030    import javax.xml.parsers.DocumentBuilder;
031    import javax.xml.parsers.DocumentBuilderFactory;
032    import javax.xml.parsers.ParserConfigurationException;
033    import java.io.IOException;
034    import java.io.InputStream;
035    import java.io.StringReader;
036    import java.util.Properties;
037    
038    public final class XmlUtils {
039    
040      public static String escape(final String s) {
041        return escape(s, true);
042      }
043    
044      public static String escape(final String s, final boolean isAttributeValue) {
045        if (null == s) {
046          return "";
047        }
048        int len = s.length();
049        StringBuilder buffer = new StringBuilder(len);
050        for (int i = 0; i < len; i++) {
051          appendEntityRef(buffer, s.charAt(i), isAttributeValue);
052        }
053        return buffer.toString();
054      }
055    
056      public static String escape(final char[] chars, final int offset, final int length, final boolean isAttributeValue) {
057        if (null == chars) {
058          return "";
059        }
060        StringBuilder buffer = new StringBuilder(length);
061        for (int i = offset; i < length; i++) {
062          appendEntityRef(buffer, chars[i], isAttributeValue);
063        }
064        return buffer.toString();
065      }
066    
067      private static void appendEntityRef(final StringBuilder buffer, final char ch,
068          final boolean isAttributeValue) {
069        // Encode special XML characters into the equivalent character references.
070        // These five are defined by default for all XML documents.
071        switch (ch) {
072          case '<':
073            buffer.append("&lt;");
074            break;
075          case '&':
076            buffer.append("&amp;");
077            break;
078          case '"': // need inside attributes values
079            if (isAttributeValue) {
080              buffer.append("&quot;");
081            } else {
082              buffer.append(ch);
083            }
084            break;
085          case '\'': // need inside attributes values
086            if (isAttributeValue) {
087              buffer.append("&apos;");
088            } else {
089              buffer.append(ch);
090            }
091            break;
092          case '>': // optional
093            buffer.append("&gt;");
094            break;
095          default:
096            buffer.append(ch);
097        }
098      }
099    
100      public static void load(final Properties properties, final InputStream stream)
101          throws IOException {
102        Document document;
103        try {
104          document = createDocument(stream);
105        } catch (SAXException e) {
106          throw new RuntimeException("Invalid properties format", e);
107        }
108        Element propertiesElement = (Element) document.getChildNodes().item(
109            document.getChildNodes().getLength() - 1);
110        importProperties(properties, propertiesElement);
111      }
112    
113      private static Document createDocument(final InputStream stream)
114          throws SAXException, IOException {
115        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
116        factory.setIgnoringElementContentWhitespace(true);
117        factory.setValidating(false);
118        factory.setCoalescing(true);
119        factory.setIgnoringComments(true);
120        try {
121          DocumentBuilder builder = factory.newDocumentBuilder();
122          builder.setEntityResolver(new Resolver());
123          InputSource source = new InputSource(stream);
124          return builder.parse(source);
125        } catch (ParserConfigurationException e) {
126          throw new Error(e);
127        }
128      }
129    
130      static void importProperties(final Properties properties, final Element propertiesElement) {
131        NodeList entries = propertiesElement.getChildNodes();
132        int numEntries = entries.getLength();
133        int start = numEntries > 0
134            && entries.item(0).getNodeName().equals("comment") ? 1 : 0;
135        for (int i = start; i < numEntries; i++) {
136          Node child = entries.item(i);
137          if (child instanceof Element) {
138            Element entry = (Element) child;
139            if (entry.hasAttribute("key")) {
140              Node node = entry.getFirstChild();
141              String value = (node == null) ? "" : node.getNodeValue();
142              properties.setProperty(entry.getAttribute("key"), value);
143            }
144          }
145        }
146      }
147    
148      private static class Resolver implements EntityResolver {
149    
150        public InputSource resolveEntity(final String publicId, final String systemId)
151            throws SAXException {
152          String dtd = "<!ELEMENT properties (comment?, entry*)>"
153              + "<!ATTLIST properties version CDATA #FIXED '1.0'>"
154              + "<!ELEMENT comment (#PCDATA)>"
155              + "<!ELEMENT entry (#PCDATA)>"
156              + "<!ATTLIST entry key CDATA #REQUIRED>";
157          InputSource inputSource = new InputSource(new StringReader(dtd));
158          inputSource.setSystemId("http://java.sun.com/dtd/properties.dtd");
159          return inputSource;
160        }
161      }
162    
163    }