/**
* 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.document.operation.automaton;
import org.waveprotocol.wave.model.util.Utf16Util;
import org.waveprotocol.wave.model.util.Utf16Util.BlipCodePointResult;
import org.waveprotocol.wave.model.util.Utf16Util.CodePointHandler;
import java.util.Collections;
import java.util.List;
/**
* Allows queries on what the XML schema allows.
*/
public interface DocumentSchema {
/**
* Defines constraints on characters
*/
public enum PermittedCharacters {
/** No characters permitted in this context */
NONE {
@Override
public String coerceString(String string) {
throw new IllegalArgumentException("Text not permitted at all, can't convert");
}
},
/** Only "blip text" characters permitted */
BLIP_TEXT {
@Override
public String coerceString(String string) {
final StringBuilder result = new StringBuilder();
Utf16Util.traverseUtf16String(string, new CodePointHandler<Void>() {
@Override
public Void codePoint(int cp) {
if (cp == '\t') {
result.append(" ");
} else if (cp == '\n' || cp == '\r') {
result.append(' ');
} else if (Utf16Util.isSupplementaryCodePoint(cp)) {
// NOTE: This will need updating when we support supplementary code points.
result.append(Utf16Util.REPLACEMENT_CHARACTER);
} else if (Utf16Util.isCodePointGoodForBlip(cp) == BlipCodePointResult.OK) {
assert 0 <= cp && cp <= 0xFFFF : "Not handling supplementary code points (yet)";
result.append((char) cp);
} else {
result.append(Utf16Util.REPLACEMENT_CHARACTER);
}
return null;
}
@Override
public Void unpairedSurrogate(char c) {
result.append(Utf16Util.REPLACEMENT_CHARACTER);
return null;
}
@Override
public Void endOfString() {
return null;
}
});
// TODO: Efficiency?!? :(
return result.toString();
}
},
/** Anything (Though well formedness requires valid unicode excluding surrogates) */
ANY {
@Override
public String coerceString(String string) {
final StringBuilder result = new StringBuilder();
Utf16Util.traverseUtf16String(string, new CodePointHandler<Void>() {
@Override
public Void codePoint(int cp) {
if (Utf16Util.isSupplementaryCodePoint(cp)) {
// NOTE: This will need updating when we support supplementary code points.
result.append(Utf16Util.REPLACEMENT_CHARACTER);
} else if (Utf16Util.isCodePointValid(cp)) {
assert 0 <= cp && cp <= 0xFFFF;
result.append((char) cp);
} else {
result.append(Utf16Util.REPLACEMENT_CHARACTER);
}
return null;
}
@Override
public Void unpairedSurrogate(char c) {
result.append(Utf16Util.REPLACEMENT_CHARACTER);
return null;
}
@Override
public Void endOfString() {
return null;
}
});
// TODO: Efficiency?!? :(
return result.toString();
}
};
/**
* Converts any string into a well formed string (with respect to the characters
* operation component) and also satisfying the particular schema constraint for
* the associated enum value.
*
* @param string Does not have to be well formed
* @return well-formed and valid
*/
public abstract String coerceString(String string);
}
/**
* True iff childType elements are permitted to occur as children of parentTypeOrNull,
* or at the top level if parentTypeOrNull is null.
*
* @param parentTypeOrNull an XML name or null.
* @param childType an XML name.
*/
boolean permitsChild(String parentTypeOrNull, String childType);
/**
* What type of text is permitted within elements of type typeOrNull, or
* at the top level if typeOrNull is null.
*
* @param typeOrNull an XML name.
*/
PermittedCharacters permittedCharacters(String typeOrNull);
/**
* True iff elements of type type permit an attribute named attributeName.
*
* @param type an XML name.
* @param attributeName an XML name.
*/
boolean permitsAttribute(String type, String attributeName);
/**
* True iff elements of type type permit an attribute named attributeName
* with the value attributeValue.
*
* @param type an XML name.
* @param attributeName an XML name.
* @param attributeValue a string.
*/
boolean permitsAttribute(String type, String attributeName, String attributeValue);
/**
* Returns a list of tag names of elements that must always occur in the given
* order at the start of the given type.
*
* @param typeOrNull
* @return list of required initial elements (may be empty)
*/
List<String> getRequiredInitialChildren(String typeOrNull);
/**
* A schema that permits anything
*/
public static final DocumentSchema NO_SCHEMA_CONSTRAINTS =
new DocumentSchema() {
@Override
public boolean permitsAttribute(String type, String attributeName) {
return true;
}
@Override
public boolean permitsAttribute(String type, String attributeName, String attributeValue) {
return true;
}
@Override
public boolean permitsChild(String parentType, String childType) {
return true;
}
@Override
public PermittedCharacters permittedCharacters(String type) {
return PermittedCharacters.ANY;
}
@Override
public List<String> getRequiredInitialChildren(String typeOrNull) {
return Collections.emptyList();
}
};
}