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 class XmlUtils {
037    
038      public static String escape(String s) {
039        return escape(s, true);
040      }
041    
042      public static String escape(String s, 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      private static void appendEntityRef(StringBuilder buffer, char ch,
055          boolean isAttributeValue) {
056        // Encode special XML characters into the equivalent character references.
057        // These five are defined by default for all XML documents.
058        switch (ch) {
059          case '<':
060            buffer.append("&lt;");
061            break;
062          case '&':
063            buffer.append("&amp;");
064            break;
065          case '"': // need inside attributes values
066            if (isAttributeValue) {
067              buffer.append("&quot;");
068            } else {
069              buffer.append(ch);
070            }
071            break;
072          case '\'': // need inside attributes values
073            if (isAttributeValue) {
074              buffer.append("&apos;");
075            } else {
076              buffer.append(ch);
077            }
078            break;
079          case '>': // optional
080            buffer.append("&gt;");
081            break;
082          default:
083            buffer.append(ch);
084        }
085      }
086    
087      public static void load(Properties properties, InputStream stream)
088          throws IOException {
089        Document document;
090        try {
091          document = createDocument(stream);
092        } catch (SAXException e) {
093          throw new RuntimeException("Invalid properties format", e);
094        }
095        Element propertiesElement = (Element) document.getChildNodes().item(
096            document.getChildNodes().getLength() - 1);
097        importProperties(properties, propertiesElement);
098      }
099    
100      private static Document createDocument(InputStream stream)
101          throws SAXException, IOException {
102        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
103        factory.setIgnoringElementContentWhitespace(true);
104        factory.setValidating(false);
105        factory.setCoalescing(true);
106        factory.setIgnoringComments(true);
107        try {
108          DocumentBuilder builder = factory.newDocumentBuilder();
109          builder.setEntityResolver(new Resolver());
110          InputSource source = new InputSource(stream);
111          return builder.parse(source);
112        } catch (ParserConfigurationException e) {
113          throw new Error(e);
114        }
115      }
116    
117      static void importProperties(Properties properties, Element propertiesElement) {
118        NodeList entries = propertiesElement.getChildNodes();
119        int numEntries = entries.getLength();
120        int start = numEntries > 0
121            && entries.item(0).getNodeName().equals("comment") ? 1 : 0;
122        for (int i = start; i < numEntries; i++) {
123          Node child = entries.item(i);
124          if (child instanceof Element) {
125            Element entry = (Element) child;
126            if (entry.hasAttribute("key")) {
127              Node node = entry.getFirstChild();
128              String value = (node == null) ? "" : node.getNodeValue();
129              properties.setProperty(entry.getAttribute("key"), value);
130            }
131          }
132        }
133      }
134    
135      private static class Resolver implements EntityResolver {
136    
137        public InputSource resolveEntity(String publicId, String systemId)
138            throws SAXException {
139          String dtd = "<!ELEMENT properties (comment?, entry*)>"
140              + "<!ATTLIST properties version CDATA #FIXED '1.0'>"
141              + "<!ELEMENT comment (#PCDATA)>"
142              + "<!ELEMENT entry (#PCDATA)>"
143              + "<!ATTLIST entry key CDATA #REQUIRED>";
144          InputSource inputSource = new InputSource(new StringReader(dtd));
145          inputSource.setSystemId("http://java.sun.com/dtd/properties.dtd");
146          return inputSource;
147        }
148      }
149    
150    }