/** * Copyright 2009 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 org.waveprotocol.wave.model.schema.conversation; import org.waveprotocol.wave.model.conversation.Blips; import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; import org.waveprotocol.wave.model.id.IdConstants; import org.waveprotocol.wave.model.id.IdUtil; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.id.WaveletIdSerializer; import org.waveprotocol.wave.model.schema.AbstractXmlSchemaConstraints; import org.waveprotocol.wave.model.schema.SchemaProvider; import org.waveprotocol.wave.model.schema.SchemaUtils; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.StringMap; import java.util.Arrays; import java.util.Collections; /** * Hard coded conversation schema constraints. * * @author danilatos@google.com (Daniel Danilatos) */ public final class ConversationSchemas implements SchemaProvider { private final StringMap<DocumentSchema> schemas; @SuppressWarnings("deprecation") public ConversationSchemas() { schemas = CollectionUtils.createStringMap(); schemas.put(IdConstants.MANIFEST_DOCUMENT_ID, MANIFEST_SCHEMA_CONSTRAINTS); } public DocumentSchema getSchemaForId(WaveletId waveletId, String documentId) { if (IdUtil.isConversationalId(waveletId)) { if (IdUtil.isBlipId(documentId)) { return BLIP_SCHEMA_CONSTRAINTS; } else if (schemas.containsKey(documentId)) { return schemas.get(documentId); } } return DocumentSchema.NO_SCHEMA_CONSTRAINTS; } /** * Hard coded ("for now") conversation document schema constraints. */ public static final DocumentSchema BLIP_SCHEMA_CONSTRAINTS = new DefaultDocumentSchema(); public static class DefaultDocumentSchema extends AbstractXmlSchemaConstraints { { addChildren(null, "head"); addChildren("head", "timestamp"); addChildren("timestamp", "lmt"); addAttrs("lmt", "t"); addChildren(null, "body"); lineContainer("body"); addAttrWithValues("line", "t", "h1", "h2", "h3", "h4", "li"); addAttrWithValues("line", "listyle", "decimal"); addAttrWithValues("line", "a", "l", "r", "c", "j"); addAttrWithValues("line", "d", "l", "r"); // NOTE: for now, value constraints for indent implemented explicitly addAttrWithValues("line", "i"); addChildren("image", "caption"); addAttrWithValues("image", "attachment"); addAttrWithValues("image", "style", "full"); addChildren("image", "gadget"); oneLiner("caption"); addChildren("caption", "reply"); oneLiner("label"); oneLiner("input"); addAttrs("reply", "id"); containsFormElements("body"); lineContainer("textarea"); addAttrs("button", "name"); addChildren("button", "caption", "events"); addChildren("events", "click"); addAttrs("click", "time", "clicker"); addAttrs("check", "name", "submit", "value"); addAttrs("radiogroup", "name", "submit", "value"); addAttrs("password", "name", "submit", "value"); addAttrs("textarea", "name", "submit", "value"); addAttrs("input", "name", "submit"); addAttrs("radio", "name", "group"); addAttrs("click", "time", "clicker"); addAttrs("label", "for"); addChildren("gadget", "title", "thumbnail", "category", "state", "pref"); // Some of these attributes might be obsolete and/or require stricter // validation addAttrs("gadget", "url", "title", "prefs", "state", "author", "height", "width", "id", "extension", "ifr", "snippet"); for (String gadgetEl : new String[] {"category", "state", "pref"}) { addAttrs(gadgetEl, "name"); } for (String gadgetEl : new String[] {"title", "thumbnail", "state", "pref"}) { addAttrs(gadgetEl, "value"); } addChildren("profile", "profile-field", "gadget"); addAttrs("profile-field", "name", "user-set"); addAttrs("profile", "avatar-url"); containsBlipText("profile-field"); addChildren("mediasearch", "result", "customsearch"); addAttrs("mediasearch", "page", "corpora", "query", "selected", "pending", "lang"); addAttrs("result", "thumbnail", "thumbwidth", "thumbheight", "content", "url", "dispurl", "title", "snippet", "num", "type", "disphtml"); addAttrs("customsearch", "name", "icon", "shortname", "resultrows", "resultcols", "addmethod"); addChildren("body", "trustreq"); containsBlipText("trustreq"); addChildren("trustreq", "trwave"); addAttrs("trustreq", "from", "numberOfWaves", "userAction"); addAttrs("trwave", "messageCount", "lastModified"); containsBlipText("trwave"); addChildren("body", "blacklist"); addAttrs("blacklist", "address", "contacts"); addChildren("body", "invitation"); addAttrs("invitation", "remaining", "title", "invitedString"); addChildren("invitation", "invited"); addAttrs("invited", "address"); addAttrWithValues("eqn", "format", "tex"); containsBlipText("eqn"); addChildren("body", "settings"); addAttrs("settings", "name"); addChildren("settings", "bool-setting", "radio-setting", "text-setting", "listbox-setting"); addAttrs("bool-setting", "id", "live-value", "saved-value"); addAttrs("radio-setting", "id", "live-value", "saved-value"); addAttrs("listbox-setting", "id", "live-value", "saved-value"); addAttrs("text-setting", "id", "saved-value"); oneLiner("text-setting"); addChildren("body", "html"); addChildren("html", "data"); containsBlipText("data"); addChildren("body", "experimental"); addAttrs("experimental", "url"); addChildren("experimental", "namevaluepair", "part"); addAttrs("part", "id"); lineContainer("part"); containsFormElements("part"); addAttrs("namevaluepair", "name"); addAttrs("namevaluepair", "value"); addChildren("body", "translation"); addChildren("translation", "stanza"); lineContainer("stanza"); addAttrs("stanza", "lang", "users"); addChildren("body", "extension_installer"); // Can it contain form elements? // TODO(user): Remove img when I know it's safe. addAttrs("extension_installer", "manifest", "img", "installed"); addChildren("body", "ext-settings"); addAttrs("ext-settings", "manifest", "enabled"); addChildren("body", "gadget-settings"); addAttrs("gadget-settings", "url", "prefs"); // NOTE: For now, schema constraints for height and width implemented // explicitly addAttrs("img", "alt", "height", "width", "src"); addChildren("body", "quote"); lineContainer("quote"); } private void lineContainer(String element) { addChildren(element, "line", "image", "gadget", "eqn", "experimental", "mediasearch", "img", "reply", "profile"); containsBlipText(element); addRequiredInitial(element, Collections.singletonList("line")); } private void oneLiner(String element) { containsBlipText(element); // Possibly allow other some elements, TBD } private void containsFormElements(String element) { addChildren(element, "button", "check", "input", "label", "password", "radiogroup", "radio", "textarea"); } @Override public boolean permitsAttribute(String tag, String attr, String value) { // Some special cases if ("line".equals(tag) && "i".equals(attr)) { return SchemaUtils.isPositiveInteger(value); } if ("img".equals(tag) && ("width".equals(attr) || "height".equals(attr))) { return SchemaUtils.isValidInteger(value, 0); } if (Blips.LAST_MODIFICATION_TIME_TAGNAME.equals(tag) && "t".equals(attr)) { return SchemaUtils.isNonNegativeInteger(value); } if ("invitation".equals(tag) && "remaining".equals(attr)) { return SchemaUtils.isNonNegativeInteger(value); } return super.permitsAttribute(tag, attr, value); } } /** * Conversation manifest document schema constraints. */ public static final DocumentSchema MANIFEST_SCHEMA_CONSTRAINTS = new AbstractXmlSchemaConstraints() { { addChildren(null, "conversation"); // Value constraints for ids and offsets implemented explicitly. addAttrs("conversation", "anchorWavelet", "anchorManifestOffset", "anchorVersion", "anchorBlip", "anchorOffset", "sort"); addChildren("conversation", "blip"); // Value constraints for blip id implemented explicitly. addAttrs("blip", "id"); addAttrWithValues("blip", "deleted", SchemaUtils.BOOLEAN_VALUES); addChildren("blip", "thread"); addChildren("blip", "peer"); addAttrs("thread", "id"); addAttrWithValues("thread", "inline", SchemaUtils.BOOLEAN_VALUES); addChildren("thread", "blip"); addAttrs("peer", "id"); } @Override public boolean permitsAttribute(String tag, String attr, String value) { // Some special cases if ("conversation".equals(tag)) { if ("anchorWavelet".equals(attr)) { return IdUtil.isConversationalId(WaveletIdSerializer.INSTANCE.fromString(value)); } else if (Arrays.asList("anchorManifestOffset", "anchorVersion", "anchorOffset") .contains(attr)) { return SchemaUtils.isNonNegativeInteger(value); } else if ("anchorBlip".equals(attr)) { return IdUtil.isBlipId(value); } } if ("blip".equals(tag) && "id".equals(attr)) { return IdUtil.isBlipId(value); } return super.permitsAttribute(tag, attr, value); } }; }