// Copyright (C) 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.ical.values; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * ical objects are made up of parameters (key=value pairs) and the contents * are often one or more value types or key=value pairs. * This schema encapsulates rules that can be applied to parse each part before * inserting the results into the {@link IcalObject}. * * @author mikesamuel+svn@gmail.com (Mike Samuel) */ class IcalSchema { /** rules for decoding parameter values */ private final Map<String, ParamRule> paramRules; /** rules for decoding parts of the content body */ private final Map<String, ContentRule> contentRules; /** rules for breaking the content body or parameters into parts */ private final Map<String, ObjectRule> objectRules; /** rules for parsing value types */ private final Map<String, XformRule> xformRules; /** list of productions that we're processing for debugging. */ private final List<String> ruleStack = new ArrayList<String>(); private static final Pattern EXTENSION_PARAM_NAME_RE = Pattern.compile("^X-[A-Z0-9\\-]+$", Pattern.CASE_INSENSITIVE); IcalSchema(Map<String, ParamRule> paramRules, Map<String, ContentRule> contentRules, Map<String, ObjectRule> objectRules, Map<String, XformRule> xformRules) { this.paramRules = paramRules; this.contentRules = contentRules; this.objectRules = objectRules; this.xformRules = xformRules; } ///////////////////////////////// // Parser Core ///////////////////////////////// public void applyParamsSchema( String rule, Map<String, String> params, IcalObject out) throws ParseException { for (Map.Entry<String, String> param : params.entrySet()) { String name = param.getKey(); applyParamSchema(rule, name, param.getValue(), out); } } public void applyParamSchema( String rule, String name, String value, IcalObject out) throws ParseException { // all elements are allowed extension parameters if (EXTENSION_PARAM_NAME_RE.matcher(name).find()) { out.getExtParams().put(name, value); return; } // if not an extension, apply the rule ruleStack.add(rule); try { (paramRules.get(rule)).apply(this, name, value, out); } finally { ruleStack.remove(ruleStack.get(ruleStack.size() - 1)); } } public void applyContentSchema(String rule, String content, IcalObject out) throws ParseException { ruleStack.add(rule); try { try { (contentRules.get(rule)).apply(this, content, out); } catch (NumberFormatException ex) { badContent(content); } catch (IllegalArgumentException ex) { badContent(content); } } finally { ruleStack.remove(ruleStack.get(ruleStack.size() - 1)); } } public void applyObjectSchema( String rule, Map<String, String> params, String content, IcalObject out) throws ParseException { ruleStack.add(rule); try { (objectRules.get(rule)).apply(this, params, content, out); } finally { ruleStack.remove(ruleStack.get(ruleStack.size() - 1)); } } public Object applyXformSchema(String rule, String content) throws ParseException { ruleStack.add(rule); try { try { return (xformRules.get(rule)).apply(this, content); } catch (NumberFormatException ex) { badContent(content); } catch (IllegalArgumentException ex) { badContent(content); } throw new AssertionError(); // badContent raises an exception } finally { ruleStack.remove(ruleStack.get(ruleStack.size() - 1)); } } ///////////////////////////////// // Parser Error Handling ///////////////////////////////// public void badParam(String name, String value) throws ParseException { throw new ParseException("parameter " + name + " has bad value [[" + value + "]] in " + ruleStack, 0); } public void badPart(String part, String msg) throws ParseException { if (null != msg) { msg = " : " + msg; } else { msg = ""; } throw new ParseException("cannot parse [[" + part + "]] in " + ruleStack + msg, 0); } public void dupePart(String part) throws ParseException { throw new ParseException( "duplicate part [[" + part + "]] in " + ruleStack, 0); } public void missingPart(String partName, String content) throws ParseException { throw new ParseException("missing part " + partName + " from [[" + content + "]] in " + ruleStack, 0); } public void badContent(String content) throws ParseException { throw new ParseException( "cannot parse content line [[" + content + "]] in " + ruleStack, 0); } /** * rule applied to parse an entire content line after its been split into * unparsed/unescaped parameters and unescaped content. */ public interface ObjectRule { /** * @param schema the schema used to provide further rules. */ public void apply(IcalSchema schema, Map<String, String> params, String content, IcalObject target) throws ParseException; } /** rule applied to an ical content line parameter. */ public interface ParamRule { public void apply( IcalSchema schema, String name, String value, IcalObject out) throws ParseException; } /** rule applied to part of the ical content body. */ public interface ContentRule { public void apply(IcalSchema schema, String content, IcalObject target) throws ParseException; } /** rule applied to parse an ical data value from its string form. */ public interface XformRule { public Object apply(IcalSchema schema, String content) throws ParseException; } }