001package biweekly.io.scribe.property;
002
003import java.util.EnumSet;
004import java.util.HashMap;
005import java.util.Map;
006import java.util.Set;
007
008import javax.xml.transform.OutputKeys;
009
010import org.w3c.dom.Document;
011import org.w3c.dom.Element;
012import org.xml.sax.SAXException;
013
014import biweekly.ICalDataType;
015import biweekly.ICalVersion;
016import biweekly.io.CannotParseException;
017import biweekly.io.ParseContext;
018import biweekly.io.WriteContext;
019import biweekly.io.json.JCalValue;
020import biweekly.io.xml.XCalElement;
021import biweekly.io.xml.XCalNamespaceContext;
022import biweekly.parameter.ICalParameters;
023import biweekly.property.Xml;
024import biweekly.util.XmlUtils;
025
026/*
027 Copyright (c) 2013-2015, Michael Angstadt
028 All rights reserved.
029
030 Redistribution and use in source and binary forms, with or without
031 modification, are permitted provided that the following conditions are met: 
032
033 1. Redistributions of source code must retain the above copyright notice, this
034 list of conditions and the following disclaimer. 
035 2. Redistributions in binary form must reproduce the above copyright notice,
036 this list of conditions and the following disclaimer in the documentation
037 and/or other materials provided with the distribution. 
038
039 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
040 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
041 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
042 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
043 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
044 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
045 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
046 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
047 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
048 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
049 */
050
051/**
052 * Marshals {@link Xml} properties.
053 * @author Michael Angstadt
054 */
055public class XmlScribe extends ICalPropertyScribe<Xml> {
056        //TODO on writing to plain text: convert to base64 if the string contains values that are illegal within a plain text value (p.17)
057        public XmlScribe() {
058                super(Xml.class, "XML", ICalDataType.TEXT);
059        }
060
061        @Override
062        protected String _writeText(Xml property, WriteContext context) {
063                Document value = property.getValue();
064                if (value != null) {
065                        String xml = valueToString(value);
066                        return escape(xml);
067                }
068
069                return "";
070        }
071
072        @Override
073        protected Xml _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
074                value = unescape(value);
075                try {
076                        return new Xml(value);
077                } catch (SAXException e) {
078                        throw new CannotParseException(29);
079                }
080        }
081
082        @Override
083        protected void _writeXml(Xml property, XCalElement element, WriteContext context) {
084                super._writeXml(property, element, context);
085                //Xml properties are handled as a special case when writing xCal documents, so this method should never get called (see: "XCalDocument" class)
086        }
087
088        @Override
089        protected Xml _parseXml(XCalElement element, ICalParameters parameters, ParseContext context) {
090                Xml xml = new Xml(element.getElement());
091
092                //remove the <parameters> element
093                Element root = XmlUtils.getRootElement(xml.getValue());
094                for (Element child : XmlUtils.toElementList(root.getChildNodes())) {
095                        if ("parameters".equals(child.getLocalName()) && XCalNamespaceContext.XCAL_NS.equals(child.getNamespaceURI())) {
096                                root.removeChild(child);
097                        }
098                }
099
100                return xml;
101        }
102
103        @Override
104        protected JCalValue _writeJson(Xml property, WriteContext context) {
105                Document value = property.getValue();
106                if (value != null) {
107                        String xml = valueToString(value);
108                        return JCalValue.single(xml);
109                }
110
111                return JCalValue.single("");
112        }
113
114        @Override
115        protected Xml _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
116                try {
117                        String xml = value.asSingle();
118                        return new Xml(xml);
119                } catch (SAXException e) {
120                        throw new CannotParseException(29);
121                }
122        }
123
124        private String valueToString(Document document) {
125                Map<String, String> props = new HashMap<String, String>();
126                props.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
127                return XmlUtils.toString(document, props);
128        }
129
130        @Override
131        public Set<ICalVersion> getSupportedVersions() {
132                return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
133        }
134}