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