src/net/fortuna/ical4j/data/HCalendarParser.java

branch
ICAL4J_EMBED_1
changeset 15
cc93757aeca3
parent 14
5ae3e5665a0b
child 18
6dcaece8ec41
     1.1 --- a/src/net/fortuna/ical4j/data/HCalendarParser.java	Thu Feb 12 18:02:00 2015 +0100
     1.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 @@ -1,558 +0,0 @@
     1.4 -/**
     1.5 - * Copyright (c) 2012, Ben Fortuna
     1.6 - * All rights reserved.
     1.7 - *
     1.8 - * Redistribution and use in source and binary forms, with or without
     1.9 - * modification, are permitted provided that the following conditions
    1.10 - * are met:
    1.11 - *
    1.12 - *  o Redistributions of source code must retain the above copyright
    1.13 - * notice, this list of conditions and the following disclaimer.
    1.14 - *
    1.15 - *  o Redistributions in binary form must reproduce the above copyright
    1.16 - * notice, this list of conditions and the following disclaimer in the
    1.17 - * documentation and/or other materials provided with the distribution.
    1.18 - *
    1.19 - *  o Neither the name of Ben Fortuna nor the names of any other contributors
    1.20 - * may be used to endorse or promote products derived from this software
    1.21 - * without specific prior written permission.
    1.22 - *
    1.23 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.24 - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.25 - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.26 - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    1.27 - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    1.28 - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    1.29 - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    1.30 - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    1.31 - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    1.32 - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    1.33 - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.34 - */
    1.35 -package net.fortuna.ical4j.data;
    1.36 -
    1.37 -import java.io.IOException;
    1.38 -import java.io.InputStream;
    1.39 -import java.io.Reader;
    1.40 -import java.net.URISyntaxException;
    1.41 -import java.text.ParseException;
    1.42 -import java.text.SimpleDateFormat;
    1.43 -import java.util.ArrayList;
    1.44 -import java.util.Iterator;
    1.45 -import java.util.List;
    1.46 -
    1.47 -import javax.xml.XMLConstants;
    1.48 -import javax.xml.parsers.DocumentBuilderFactory;
    1.49 -import javax.xml.parsers.ParserConfigurationException;
    1.50 -import javax.xml.xpath.XPath;
    1.51 -import javax.xml.xpath.XPathConstants;
    1.52 -import javax.xml.xpath.XPathException;
    1.53 -import javax.xml.xpath.XPathExpression;
    1.54 -import javax.xml.xpath.XPathFactory;
    1.55 -
    1.56 -import net.fortuna.ical4j.model.CalendarException;
    1.57 -import net.fortuna.ical4j.model.Component;
    1.58 -import net.fortuna.ical4j.model.Date;
    1.59 -import net.fortuna.ical4j.model.DateTime;
    1.60 -import net.fortuna.ical4j.model.Parameter;
    1.61 -import net.fortuna.ical4j.model.Property;
    1.62 -import net.fortuna.ical4j.model.parameter.Value;
    1.63 -import net.fortuna.ical4j.model.property.Version;
    1.64 -
    1.65 -import org.apache.commons.lang3.StringUtils;
    1.66 -import org.apache.commons.logging.Log;
    1.67 -import org.apache.commons.logging.LogFactory;
    1.68 -import org.w3c.dom.DOMException;
    1.69 -import org.w3c.dom.Document;
    1.70 -import org.w3c.dom.Element;
    1.71 -import org.w3c.dom.Node;
    1.72 -import org.w3c.dom.NodeList;
    1.73 -import org.xml.sax.InputSource;
    1.74 -import org.xml.sax.SAXException;
    1.75 -import org.xml.sax.SAXParseException;
    1.76 -
    1.77 -/**
    1.78 - * A {@link CalendarParser} that parses XHTML documents that include calendar data marked up with the hCalendar
    1.79 - * microformat.
    1.80 - * <p>
    1.81 - * The parser treats the entire document as a single "vcalendar" context, ignoring any <code>vcalendar</code> elements
    1.82 - * and adding all components in the document to a single generated calendar.
    1.83 - * </p>
    1.84 - * <p>
    1.85 - * Since hCalendar does not include product information, the <code>PRODID</code> property is omitted from the generated
    1.86 - * calendar. The hCalendar profile is supposed to define the iCalendar version that it represents, but it does not, so
    1.87 - * version 2.0 is assumed.
    1.88 - * </p>
    1.89 - * <h3>Supported Components</h3>
    1.90 - * <p>
    1.91 - * This parser recognizes only "vevent" components.
    1.92 - * </p>
    1.93 - * <h3>Supported Properties</h3>
    1.94 - * <p>
    1.95 - * This parser recognizes the following properties:
    1.96 - * </p>
    1.97 - * <ul>
    1.98 - * <li>"dtstart"</li>
    1.99 - * <li>"dtend"</li>
   1.100 - * <li>"duration"</li>
   1.101 - * <li>"summary"</li>
   1.102 - * <li>"uid"</li>
   1.103 - * <li>"dtstamp"</li>
   1.104 - * <li>"category"</li>
   1.105 - * <li>"location"</li>
   1.106 - * <li>"url"</li>
   1.107 - * <li>"description"</li>
   1.108 - * <li>"last-modified"</li>
   1.109 - * <li>"status"</li>
   1.110 - * <li>"class"</li>
   1.111 - * <li>"attendee"</li>
   1.112 - * <li>"contact"</li>
   1.113 - * <li>"organizer"</li>
   1.114 - * </ul>
   1.115 - * <p>
   1.116 - * hCalendar allows for some properties to be represented by nested microformat records, including hCard, adr and geo.
   1.117 - * This parser does not recognize these records. It simply accumulates the text content of any child elements of the
   1.118 - * property element and uses the resulting string as the property value.
   1.119 - * </p>
   1.120 - * <h4>Date and Date-Time Properties</h4>
   1.121 - * <p>
   1.122 - * hCalendar date-time values are formatted according to RFC 3339. There is no representation in this specification for
   1.123 - * time zone ids. All date-times are specified either in UTC or with an offset that can be used to convert the local
   1.124 - * time into UTC. Neither does hCal provide a reprsentation for floating date-times. Therefore, all date-time values
   1.125 - * produced by this parser are in UTC.
   1.126 - * </p>
   1.127 - * <p>
   1.128 - * Some examples in the wild provide date and date-time values in iCalendar format rather than RFC 3339 format. Although
   1.129 - * not technically legal according to spec, these values are accepted. In this case, floating date-times are produced by
   1.130 - * the parser.
   1.131 - * </p>
   1.132 - * <h3>Supported Parameters</h3>
   1.133 - * <p>
   1.134 - * hCalendar does not define attributes, nested elements or other information elements representing parameter data.
   1.135 - * Therefore, this parser does not set any property parameters except as implied by property value data (e.g.
   1.136 - * VALUE=DATE-TIME or VALUE=DATE for date-time properties).
   1.137 - * </p>
   1.138 - */
   1.139 -public class HCalendarParser implements CalendarParser {
   1.140 -    
   1.141 -    private static final Log LOG = LogFactory.getLog(HCalendarParser.class);
   1.142 -    
   1.143 -    private static final DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
   1.144 -    private static final XPath XPATH = XPathFactory.newInstance().newXPath();
   1.145 -    private static final XPathExpression XPATH_METHOD;
   1.146 -    private static final XPathExpression XPATH_VEVENTS;
   1.147 -    private static final XPathExpression XPATH_DTSTART;
   1.148 -    private static final XPathExpression XPATH_DTEND;
   1.149 -    private static final XPathExpression XPATH_DURATION;
   1.150 -    private static final XPathExpression XPATH_SUMMARY;
   1.151 -    private static final XPathExpression XPATH_UID;
   1.152 -    private static final XPathExpression XPATH_DTSTAMP;
   1.153 -    private static final XPathExpression XPATH_CATEGORY;
   1.154 -    private static final XPathExpression XPATH_LOCATION;
   1.155 -    private static final XPathExpression XPATH_URL;
   1.156 -    private static final XPathExpression XPATH_DESCRIPTION;
   1.157 -    private static final XPathExpression XPATH_LAST_MODIFIED;
   1.158 -    private static final XPathExpression XPATH_STATUS;
   1.159 -    private static final XPathExpression XPATH_CLASS;
   1.160 -    private static final XPathExpression XPATH_ATTENDEE;
   1.161 -    private static final XPathExpression XPATH_CONTACT;
   1.162 -    private static final XPathExpression XPATH_ORGANIZER;
   1.163 -    private static final XPathExpression XPATH_SEQUENCE;
   1.164 -    private static final XPathExpression XPATH_ATTACH;
   1.165 -    private static final String HCAL_DATE_PATTERN = "yyyy-MM-dd";
   1.166 -    private static final SimpleDateFormat HCAL_DATE_FORMAT = new SimpleDateFormat(HCAL_DATE_PATTERN);
   1.167 -    private static final String HCAL_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssz";
   1.168 -    private static final SimpleDateFormat HCAL_DATE_TIME_FORMAT = new SimpleDateFormat(HCAL_DATE_TIME_PATTERN);
   1.169 -
   1.170 -    static {
   1.171 -        BUILDER_FACTORY.setNamespaceAware(true);
   1.172 -        BUILDER_FACTORY.setIgnoringComments(true);
   1.173 -
   1.174 -        XPATH_METHOD = compileExpression("//*[contains(@class, 'method')]");
   1.175 -        XPATH_VEVENTS = compileExpression("//*[contains(@class, 'vevent')]");
   1.176 -        XPATH_DTSTART = compileExpression(".//*[contains(@class, 'dtstart')]");
   1.177 -        XPATH_DTEND = compileExpression(".//*[contains(@class, 'dtend')]");
   1.178 -        XPATH_DURATION = compileExpression(".//*[contains(@class, 'duration')]");
   1.179 -        XPATH_SUMMARY = compileExpression(".//*[contains(@class, 'summary')]");
   1.180 -        XPATH_UID = compileExpression(".//*[contains(@class, 'uid')]");
   1.181 -        XPATH_DTSTAMP = compileExpression(".//*[contains(@class, 'dtstamp')]");
   1.182 -        XPATH_CATEGORY = compileExpression(".//*[contains(@class, 'category')]");
   1.183 -        XPATH_LOCATION = compileExpression(".//*[contains(@class, 'location')]");
   1.184 -        XPATH_URL = compileExpression(".//*[contains(@class, 'url')]");
   1.185 -        XPATH_DESCRIPTION = compileExpression(".//*[contains(@class, 'description')]");
   1.186 -        XPATH_LAST_MODIFIED = compileExpression(".//*[contains(@class, 'last-modified')]");
   1.187 -        XPATH_STATUS = compileExpression(".//*[contains(@class, 'status')]");
   1.188 -        XPATH_CLASS = compileExpression(".//*[contains(@class, 'class')]");
   1.189 -        XPATH_ATTENDEE = compileExpression(".//*[contains(@class, 'attendee')]");
   1.190 -        XPATH_CONTACT = compileExpression(".//*[contains(@class, 'contact')]");
   1.191 -        XPATH_ORGANIZER = compileExpression(".//*[contains(@class, 'organizer')]");
   1.192 -        XPATH_SEQUENCE = compileExpression(".//*[contains(@class, 'sequence')]");
   1.193 -        XPATH_ATTACH = compileExpression(".//*[contains(@class, 'attach')]");
   1.194 -    }
   1.195 -
   1.196 -    private static XPathExpression compileExpression(String expr) {
   1.197 -        try {
   1.198 -            return XPATH.compile(expr);
   1.199 -        } catch (XPathException e) {
   1.200 -            throw new CalendarException(e);
   1.201 -        }
   1.202 -    }
   1.203 -
   1.204 -    /**
   1.205 -     * {@inheritDoc}
   1.206 -     */
   1.207 -    public void parse(InputStream in, ContentHandler handler) throws IOException, ParserException {
   1.208 -        parse(new InputSource(in), handler);
   1.209 -    }
   1.210 -
   1.211 -    /**
   1.212 -     * {@inheritDoc}
   1.213 -     */
   1.214 -    public void parse(Reader in, ContentHandler handler) throws IOException, ParserException {
   1.215 -        parse(new InputSource(in), handler);
   1.216 -    }
   1.217 -
   1.218 -    private void parse(InputSource in, ContentHandler handler) throws IOException, ParserException {
   1.219 -        try {
   1.220 -            Document d = BUILDER_FACTORY.newDocumentBuilder().parse(in);
   1.221 -            buildCalendar(d, handler);
   1.222 -        } catch (ParserConfigurationException e) {
   1.223 -            throw new CalendarException(e);
   1.224 -        } catch (SAXException e) {
   1.225 -            if (e instanceof SAXParseException) {
   1.226 -                SAXParseException pe = (SAXParseException) e;
   1.227 -                throw new ParserException("Could not parse XML", pe.getLineNumber(), e);
   1.228 -            }
   1.229 -            throw new ParserException(e.getMessage(), -1, e);
   1.230 -        }
   1.231 -    }
   1.232 -
   1.233 -    private static NodeList findNodes(XPathExpression expr, Object context) throws ParserException {
   1.234 -        try {
   1.235 -            return (NodeList) expr.evaluate(context, XPathConstants.NODESET);
   1.236 -        } catch (XPathException e) {
   1.237 -            throw new ParserException("Unable to find nodes", -1, e);
   1.238 -        }
   1.239 -    }
   1.240 -
   1.241 -    private static Node findNode(XPathExpression expr, Object context) throws ParserException {
   1.242 -        try {
   1.243 -            return (Node) expr.evaluate(context, XPathConstants.NODE);
   1.244 -        } catch (XPathException e) {
   1.245 -            throw new ParserException("Unable to find node", -1, e);
   1.246 -        }
   1.247 -    }
   1.248 -
   1.249 -    private static List findElements(XPathExpression expr, Object context) throws ParserException {
   1.250 -        NodeList nodes = findNodes(expr, context);
   1.251 -        ArrayList elements = new ArrayList();
   1.252 -        for (int i = 0; i < nodes.getLength(); i++) {
   1.253 -            Node n = nodes.item(i);
   1.254 -            if (n instanceof Element)
   1.255 -                elements.add((Element) n);
   1.256 -        }
   1.257 -        return elements;
   1.258 -    }
   1.259 -
   1.260 -    private static Element findElement(XPathExpression expr, Object context) throws ParserException {
   1.261 -        Node n = findNode(expr, context);
   1.262 -        if (n == null || (!(n instanceof Element)))
   1.263 -            return null;
   1.264 -        return (Element) n;
   1.265 -    }
   1.266 -
   1.267 -    private static String getTextContent(Element element) throws ParserException {
   1.268 -        try {
   1.269 -            String content = element.getFirstChild().getNodeValue();
   1.270 -            if (content != null) {
   1.271 -                return content.trim().replaceAll("\\s+", " ");
   1.272 -            }
   1.273 -            return content;
   1.274 -        } catch (DOMException e) {
   1.275 -            throw new ParserException("Unable to get text content for element " + element.getNodeName(), -1, e);
   1.276 -        }
   1.277 -    }
   1.278 -
   1.279 -    private void buildCalendar(Document d, ContentHandler handler) throws ParserException {
   1.280 -        // "The root class name for hCalendar is "vcalendar". An element with a
   1.281 -        // class name of "vcalendar" is itself called an hCalendar.
   1.282 -        //
   1.283 -        // The root class name for events is "vevent". An element with a class
   1.284 -        // name of "vevent" is itself called an hCalender event.
   1.285 -        //
   1.286 -        // For authoring convenience, both "vevent" and "vcalendar" are
   1.287 -        // treated as root class names for parsing purposes. If a document
   1.288 -        // contains elements with class name "vevent" but not "vcalendar", the
   1.289 -        // entire document has an implied "vcalendar" context."
   1.290 -
   1.291 -        // XXX: We assume that the entire document has a single vcalendar
   1.292 -        // context. It is possible that the document contains more than one
   1.293 -        // vcalendar element. In this case, we should probably only process
   1.294 -        // that element and log a warning about skipping the others.
   1.295 -
   1.296 -        if (LOG.isDebugEnabled())
   1.297 -            LOG.debug("Building calendar");
   1.298 -
   1.299 -        handler.startCalendar();
   1.300 -
   1.301 -        // no PRODID, as the using application should set that itself
   1.302 -
   1.303 -        handler.startProperty(Property.VERSION);
   1.304 -        try {
   1.305 -            handler.propertyValue(Version.VERSION_2_0.getValue());
   1.306 -        } catch (Exception e) {
   1.307 -        }
   1.308 -        ;
   1.309 -        handler.endProperty(Property.VERSION);
   1.310 -
   1.311 -        Element method = findElement(XPATH_METHOD, d);
   1.312 -        if (method != null) {
   1.313 -            buildProperty(method, Property.METHOD, handler);
   1.314 -        }
   1.315 -
   1.316 -        List vevents = findElements(XPATH_VEVENTS, d);
   1.317 -        for (Iterator i = vevents.iterator(); i.hasNext();) {
   1.318 -            Element vevent = (Element) i.next();
   1.319 -            buildEvent(vevent, handler);
   1.320 -        }
   1.321 -
   1.322 -        // XXX: support other "first class components": vjournal, vtodo,
   1.323 -        // vfreebusy, vavailability, vvenue
   1.324 -
   1.325 -        handler.endCalendar();
   1.326 -    }
   1.327 -
   1.328 -    private void buildEvent(Element element, ContentHandler handler) throws ParserException {
   1.329 -        if (LOG.isDebugEnabled())
   1.330 -            LOG.debug("Building event");
   1.331 -
   1.332 -        handler.startComponent(Component.VEVENT);
   1.333 -
   1.334 -        buildProperty(findElement(XPATH_DTSTART, element), Property.DTSTART, handler);
   1.335 -        buildProperty(findElement(XPATH_DTEND, element), Property.DTEND, handler);
   1.336 -        buildProperty(findElement(XPATH_DURATION, element), Property.DURATION, handler);
   1.337 -        buildProperty(findElement(XPATH_SUMMARY, element), Property.SUMMARY, handler);
   1.338 -        buildProperty(findElement(XPATH_UID, element), Property.UID, handler);
   1.339 -        buildProperty(findElement(XPATH_DTSTAMP, element), Property.DTSTAMP, handler);
   1.340 -        List categories = findElements(XPATH_CATEGORY, element);
   1.341 -        for (Iterator i = categories.iterator(); i.hasNext();) {
   1.342 -            Element category = (Element) i.next();
   1.343 -            buildProperty(category, Property.CATEGORIES, handler);
   1.344 -        }
   1.345 -        buildProperty(findElement(XPATH_LOCATION, element), Property.LOCATION, handler);
   1.346 -        buildProperty(findElement(XPATH_URL, element), Property.URL, handler);
   1.347 -        buildProperty(findElement(XPATH_DESCRIPTION, element), Property.DESCRIPTION, handler);
   1.348 -        buildProperty(findElement(XPATH_LAST_MODIFIED, element), Property.LAST_MODIFIED, handler);
   1.349 -        buildProperty(findElement(XPATH_STATUS, element), Property.STATUS, handler);
   1.350 -        buildProperty(findElement(XPATH_CLASS, element), Property.CLASS, handler);
   1.351 -        List attendees = findElements(XPATH_ATTENDEE, element);
   1.352 -        for (Iterator i = attendees.iterator(); i.hasNext();) {
   1.353 -            Element attendee = (Element) i.next();
   1.354 -            buildProperty(attendee, Property.ATTENDEE, handler);
   1.355 -        }
   1.356 -        buildProperty(findElement(XPATH_CONTACT, element), Property.CONTACT, handler);
   1.357 -        buildProperty(findElement(XPATH_ORGANIZER, element), Property.ORGANIZER, handler);
   1.358 -        buildProperty(findElement(XPATH_SEQUENCE, element), Property.SEQUENCE, handler);
   1.359 -        buildProperty(findElement(XPATH_ATTACH, element), Property.ATTACH, handler);
   1.360 -
   1.361 -        handler.endComponent(Component.VEVENT);
   1.362 -    }
   1.363 -
   1.364 -    private void buildProperty(Element element, String propName, ContentHandler handler) throws ParserException {
   1.365 -        if (element == null)
   1.366 -            return;
   1.367 -
   1.368 -        if (LOG.isDebugEnabled())
   1.369 -            LOG.debug("Building property " + propName);
   1.370 -
   1.371 -        String className = className(propName);
   1.372 -        String elementName = element.getLocalName().toLowerCase();
   1.373 -
   1.374 -        String value = null;
   1.375 -        if (elementName.equals("abbr")) {
   1.376 -            // "If an <abbr> element is used for a property, then the 'title'
   1.377 -            // attribute of the <abbr> element is the value of the property,
   1.378 -            // instead of the contents of the element, which instead provide a
   1.379 -            // human presentable version of the value."
   1.380 -            value = element.getAttribute("title");
   1.381 -            if (StringUtils.isBlank(value))
   1.382 -                throw new ParserException("Abbr element '" + className + "' requires a non-empty title", -1);
   1.383 -            if (LOG.isDebugEnabled())
   1.384 -                LOG.debug("Setting value '" + value + "' from title attribute");
   1.385 -        } else if (isHeaderElement(elementName)) {
   1.386 -            // try title first. if that's not set, fall back to text content.
   1.387 -            value = element.getAttribute("title");
   1.388 -            if (!StringUtils.isBlank(value)) {
   1.389 -                if (LOG.isDebugEnabled())
   1.390 -                    LOG.debug("Setting value '" + value + "' from title attribute");
   1.391 -            } else {
   1.392 -                value = getTextContent(element);
   1.393 -                if (LOG.isDebugEnabled())
   1.394 -                    LOG.debug("Setting value '" + value + "' from text content");
   1.395 -            }
   1.396 -        } else if (elementName.equals("a") && isUrlProperty(propName)) {
   1.397 -            value = element.getAttribute("href");
   1.398 -            if (StringUtils.isBlank(value))
   1.399 -                throw new ParserException("A element '" + className + "' requires a non-empty href", -1);
   1.400 -            if (LOG.isDebugEnabled())
   1.401 -                LOG.debug("Setting value '" + value + "' from href attribute");
   1.402 -        } else if (elementName.equals("img")) {
   1.403 -            if (isUrlProperty(propName)) {
   1.404 -                value = element.getAttribute("src");
   1.405 -                if (StringUtils.isBlank(value))
   1.406 -                    throw new ParserException("Img element '" + className + "' requires a non-empty src", -1);
   1.407 -                if (LOG.isDebugEnabled())
   1.408 -                    LOG.debug("Setting value '" + value + "' from src attribute");
   1.409 -            } else {
   1.410 -                value = element.getAttribute("alt");
   1.411 -                if (StringUtils.isBlank(value))
   1.412 -                    throw new ParserException("Img element '" + className + "' requires a non-empty alt", -1);
   1.413 -                if (LOG.isDebugEnabled())
   1.414 -                    LOG.debug("Setting value '" + value + "' from alt attribute");
   1.415 -            }
   1.416 -        } else {
   1.417 -            value = getTextContent(element);
   1.418 -            if (!StringUtils.isBlank(value)) {
   1.419 -                if (LOG.isDebugEnabled())
   1.420 -                    LOG.debug("Setting value '" + value + "' from text content");
   1.421 -            }
   1.422 -        }
   1.423 -
   1.424 -        if (StringUtils.isBlank(value)) {
   1.425 -            if (LOG.isDebugEnabled())
   1.426 -                LOG.debug("Skipping property with empty value");
   1.427 -            return;
   1.428 -        }
   1.429 -
   1.430 -        handler.startProperty(propName);
   1.431 -
   1.432 -        // if it's a date property, we have to convert from the
   1.433 -        // hCalendar-formatted date (RFC 3339) to an iCalendar-formatted date
   1.434 -        if (isDateProperty(propName)) {
   1.435 -            try {
   1.436 -                Date date = icalDate(value);
   1.437 -                value = date.toString();
   1.438 -
   1.439 -                if (!(date instanceof DateTime))
   1.440 -                    try {
   1.441 -                        handler.parameter(Parameter.VALUE, Value.DATE.getValue());
   1.442 -                    } catch (Exception e) {
   1.443 -                    }
   1.444 -            } catch (ParseException e) {
   1.445 -                throw new ParserException("Malformed date value for element '" + className + "'", -1, e);
   1.446 -            }
   1.447 -        }
   1.448 -
   1.449 -        if (isTextProperty(propName)) {
   1.450 -            String lang = element.getAttributeNS(XMLConstants.XML_NS_URI, "lang");
   1.451 -            if (!StringUtils.isBlank(lang))
   1.452 -                try {
   1.453 -                    handler.parameter(Parameter.LANGUAGE, lang);
   1.454 -                } catch (Exception e) {
   1.455 -                }
   1.456 -        }
   1.457 -
   1.458 -        // XXX: other parameters?
   1.459 -
   1.460 -        try {
   1.461 -            handler.propertyValue(value);
   1.462 -        } catch (URISyntaxException e) {
   1.463 -            throw new ParserException("Malformed URI value for element '" + className + "'", -1, e);
   1.464 -        } catch (ParseException e) {
   1.465 -            throw new ParserException("Malformed value for element '" + className + "'", -1, e);
   1.466 -        } catch (IOException e) {
   1.467 -            throw new CalendarException(e);
   1.468 -        }
   1.469 -
   1.470 -        handler.endProperty(propName);
   1.471 -    }
   1.472 -
   1.473 -    // "The basic format of hCalendar is to use iCalendar object/property
   1.474 -    // names in lower-case for class names ..."
   1.475 -    /*
   1.476 -     * private static String _icalName(Element element) { return element.getAttribute("class").toUpperCase(); }
   1.477 -     */
   1.478 -
   1.479 -    private static String className(String propName) {
   1.480 -        return propName.toLowerCase();
   1.481 -    }
   1.482 -
   1.483 -    private static boolean isHeaderElement(String name) {
   1.484 -        return (name.equals("h1") || name.equals("h2") || name.equals("h3")
   1.485 -                || name.equals("h4") || name.equals("h5") || name
   1.486 -                .equals("h6"));
   1.487 -    }
   1.488 -
   1.489 -    private static boolean isDateProperty(String name) {
   1.490 -        return (name.equals(Property.DTSTART) || name.equals(Property.DTEND) || name.equals(Property.DTSTAMP) || name
   1.491 -                .equals(Property.LAST_MODIFIED));
   1.492 -    }
   1.493 -
   1.494 -    private static boolean isUrlProperty(String name) {
   1.495 -        return (name.equals(Property.URL));
   1.496 -    }
   1.497 -
   1.498 -    private static boolean isTextProperty(String name) {
   1.499 -        return (name.equals(Property.SUMMARY) || name.equals(Property.LOCATION) || name.equals(Property.CATEGORIES)
   1.500 -                || name.equals(Property.DESCRIPTION) || name.equals(Property.ATTENDEE)
   1.501 -                || name.equals(Property.CONTACT) || name
   1.502 -                .equals(Property.ORGANIZER));
   1.503 -    }
   1.504 -
   1.505 -    private static Date icalDate(String original) throws ParseException {
   1.506 -        // in the real world, some generators use iCalendar formatted
   1.507 -        // dates and date-times, so try parsing those formats first before
   1.508 -        // going to RFC 3339 formats
   1.509 -
   1.510 -        if (original.indexOf('T') == -1) {
   1.511 -            // date-only
   1.512 -            try {
   1.513 -                // for some reason Date's pattern matches yyyy-MM-dd, so
   1.514 -                // don't check it if we find -
   1.515 -                if (original.indexOf('-') == -1)
   1.516 -                    return new Date(original);
   1.517 -            } catch (Exception e) {
   1.518 -            }
   1.519 -            return new Date(HCAL_DATE_FORMAT.parse(original));
   1.520 -        }
   1.521 -
   1.522 -        try {
   1.523 -            return new DateTime(original);
   1.524 -        } catch (Exception e) {
   1.525 -        }
   1.526 -
   1.527 -        // the date-time value can represent its time zone in a few different
   1.528 -        // ways. we have to normalize those to match our pattern.
   1.529 -
   1.530 -        String normalized = null;
   1.531 -
   1.532 -        if (LOG.isDebugEnabled())
   1.533 -            LOG.debug("normalizing date-time " + original);
   1.534 -
   1.535 -        // 2002-10-09T19:00:00Z
   1.536 -        if (original.charAt(original.length() - 1) == 'Z') {
   1.537 -            normalized = original.replaceAll("Z", "GMT-00:00");
   1.538 -        }
   1.539 -        // 2002-10-10T00:00:00+05:00
   1.540 -        else if (original.indexOf("GMT") == -1
   1.541 -                && (original.charAt(original.length() - 6) == '+' || original.charAt(original.length() - 6) == '-')) {
   1.542 -            String tzId = "GMT" + original.substring(original.length() - 6);
   1.543 -            normalized = original.substring(0, original.length() - 6) + tzId;
   1.544 -        } else {
   1.545 -            // 2002-10-10T00:00:00GMT+05:00
   1.546 -            normalized = original;
   1.547 -        }
   1.548 -
   1.549 -        DateTime dt = new DateTime(HCAL_DATE_TIME_FORMAT.parse(normalized));
   1.550 -
   1.551 -        // hCalendar does not specify a representation for timezone ids
   1.552 -        // or any other sort of timezone information. the best it does is
   1.553 -        // give us a timezone offset that we can use to convert the local
   1.554 -        // time to UTC. furthermore, it has no representation for floating
   1.555 -        // date-times. therefore, all dates are converted to UTC.
   1.556 -
   1.557 -        dt.setUtc(true);
   1.558 -
   1.559 -        return dt;
   1.560 -    }
   1.561 -}

mercurial