| /* | |
| * Copyright 2008 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.gwt.uibinder.rebind; | |
| import com.google.gwt.core.ext.UnableToCompleteException; | |
| import com.google.gwt.core.ext.typeinfo.JClassType; | |
| import com.google.gwt.core.ext.typeinfo.JType; | |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; | |
| import com.google.gwt.core.ext.typeinfo.TypeOracleException; | |
| import com.google.gwt.dom.client.Style.Unit; | |
| import com.google.gwt.resources.client.ImageResource; | |
| import com.google.gwt.uibinder.attributeparsers.AttributeParser; | |
| import com.google.gwt.uibinder.attributeparsers.AttributeParsers; | |
| import org.w3c.dom.Attr; | |
| import org.w3c.dom.Element; | |
| import org.w3c.dom.NamedNodeMap; | |
| import org.w3c.dom.Node; | |
| import org.w3c.dom.NodeList; | |
| import org.w3c.dom.Text; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| /** | |
| * A wrapper for {@link Element} that limits the way parsers can interact with | |
| * the XML document, and provides some convenience methods. | |
| * <p> | |
| * The main function of this wrapper is to ensure that parsers can only read | |
| * elements and attributes by 'consuming' them, which removes the given value. | |
| * This allows for a natural hierarchy among parsers -- more specific parsers | |
| * will run first, and if they consume a value, less-specific parsers will not | |
| * see it. | |
| */ | |
| public class XMLElement { | |
| /** | |
| * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and | |
| * {@link #consumeChildElements(Interpreter)}. | |
| */ | |
| public interface Interpreter<T> { | |
| /** | |
| * Given an XMLElement, return its filtered value. | |
| * | |
| * @throws UnableToCompleteException on error | |
| */ | |
| T interpretElement(XMLElement elem) throws UnableToCompleteException; | |
| } | |
| /** | |
| * Extends {@link Interpreter} with a method to be called after all elements | |
| * have been processed. | |
| */ | |
| public interface PostProcessingInterpreter<T> extends Interpreter<T> { | |
| String postProcess(String consumedText) throws UnableToCompleteException; | |
| } | |
| private static class NoBrainInterpeter<T> implements Interpreter<T> { | |
| private final T rtn; | |
| public NoBrainInterpeter(T rtn) { | |
| this.rtn = rtn; | |
| } | |
| public T interpretElement(XMLElement elem) { | |
| return rtn; | |
| } | |
| } | |
| /** | |
| * Represents the source location where the XMLElement was declared. | |
| */ | |
| public static class Location { | |
| private final String systemId; | |
| private final int lineNumber; | |
| public Location(String systemId, int lineNumber) { | |
| this.systemId = systemId; | |
| this.lineNumber = lineNumber; | |
| } | |
| public int getLineNumber() { | |
| return lineNumber; | |
| } | |
| public String getSystemId() { | |
| return systemId; | |
| } | |
| /** | |
| * For debugging use only. | |
| */ | |
| @Override | |
| public String toString() { | |
| return systemId + ":" + lineNumber; | |
| } | |
| } | |
| static final String LOCATION_KEY = "gwtLocation"; | |
| private static final Set<String> NO_END_TAG = new HashSet<String>(); | |
| private static final String[] EMPTY = new String[]{}; | |
| private static void clearChildren(Element elem) { | |
| // TODO(rjrjr) I'm nearly positive that anywhere this is called | |
| // we should instead be calling assertNoBody | |
| Node child; | |
| while ((child = elem.getFirstChild()) != null) { | |
| elem.removeChild(child); | |
| } | |
| } | |
| private final Element elem; | |
| private final AttributeParsers attributeParsers; | |
| // for legacy templates | |
| @SuppressWarnings("deprecation") | |
| private final com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers bundleParsers; | |
| private final TypeOracle oracle; | |
| private final MortalLogger logger; | |
| private final String debugString; | |
| private final DesignTimeUtils designTime; | |
| private final XMLElementProvider provider; | |
| private JType booleanType; | |
| private JType imageResourceType; | |
| private JType doubleType; | |
| private JType intType; | |
| private JType stringType; | |
| { | |
| // from com/google/gxp/compiler/schema/html.xml | |
| NO_END_TAG.add("area"); | |
| NO_END_TAG.add("base"); | |
| NO_END_TAG.add("basefont"); | |
| NO_END_TAG.add("br"); | |
| NO_END_TAG.add("col"); | |
| NO_END_TAG.add("frame"); | |
| NO_END_TAG.add("hr"); | |
| NO_END_TAG.add("img"); | |
| NO_END_TAG.add("input"); | |
| NO_END_TAG.add("isindex"); | |
| NO_END_TAG.add("link"); | |
| NO_END_TAG.add("meta"); | |
| NO_END_TAG.add("param"); | |
| NO_END_TAG.add("wbr"); | |
| } | |
| // bundleParsers for legacy templates | |
| @SuppressWarnings("deprecation") | |
| XMLElement( | |
| Element elem, | |
| AttributeParsers attributeParsers, | |
| com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers bundleParsers, | |
| TypeOracle oracle, MortalLogger logger, DesignTimeUtils designTime, | |
| XMLElementProvider provider) { | |
| this.elem = elem; | |
| this.attributeParsers = attributeParsers; | |
| this.bundleParsers = bundleParsers; | |
| this.oracle = oracle; | |
| this.logger = logger; | |
| this.designTime = designTime; | |
| this.provider = provider; | |
| this.debugString = getOpeningTag(); | |
| } | |
| /** | |
| * Ensure that the receiver has no attributes left. | |
| * | |
| * @throws UnableToCompleteException if it does | |
| */ | |
| public void assertNoAttributes() throws UnableToCompleteException { | |
| int numAtts = getAttributeCount(); | |
| if (numAtts == 0) { | |
| return; | |
| } | |
| StringBuilder b = new StringBuilder(); | |
| for (int i = 0; i < numAtts; i++) { | |
| if (i > 0) { | |
| b.append(", "); | |
| } | |
| b.append('"').append(getAttribute(i).getName()).append('"'); | |
| } | |
| logger.die(this, "Unexpected attributes: %s", b); | |
| } | |
| /** | |
| * Require that the receiver's body is empty of text and has no child nodes. | |
| * | |
| * @throws UnableToCompleteException if it isn't | |
| */ | |
| public void assertNoBody() throws UnableToCompleteException { | |
| consumeChildElements(new Interpreter<Boolean>() { | |
| public Boolean interpretElement(XMLElement elem) | |
| throws UnableToCompleteException { | |
| logger.die(elem, "Found unexpected child element"); | |
| return false; // unreachable | |
| } | |
| }); | |
| assertNoText(); | |
| } | |
| /** | |
| * Require that the receiver's body is empty of text. | |
| * | |
| * @throws UnableToCompleteException if it isn't | |
| */ | |
| public void assertNoText() throws UnableToCompleteException { | |
| NoBrainInterpeter<String> nullInterpreter = new NoBrainInterpeter<String>( | |
| null); | |
| String s = consumeInnerTextEscapedAsHtmlStringLiteral(nullInterpreter); | |
| if (!"".equals(s)) { | |
| logger.die(this, "Unexpected text in element: \"%s\"", s); | |
| } | |
| } | |
| /** | |
| * Consumes the given attribute as a literal or field reference. The type | |
| * parameter is required to determine how the value is parsed and validated. | |
| * | |
| * @param name the attribute's full name (including prefix) | |
| * @param type the type this attribute is expected to provide | |
| * @return the attribute's value as a Java expression, or null if it is not | |
| * set | |
| * @throws UnableToCompleteException on parse failure | |
| */ | |
| public String consumeAttribute(String name, JType type) | |
| throws UnableToCompleteException { | |
| return consumeAttributeWithDefault(name, null, type); | |
| } | |
| /** | |
| * Consumes the given attribute as a literal or field reference. The type | |
| * parameter is required to determine how the value is parsed and validated. | |
| * | |
| * @param name the attribute's full name (including prefix) | |
| * @param defaultValue the value to @return if the attribute was unset | |
| * @param type the type this attribute is expected to provide | |
| * @return the attribute's value as a Java expression, or the given default if | |
| * it was unset | |
| * @throws UnableToCompleteException on parse failure | |
| */ | |
| public String consumeAttributeWithDefault(String name, String defaultValue, | |
| JType type) throws UnableToCompleteException { | |
| return consumeAttributeWithDefault(name, defaultValue, new JType[]{type}); | |
| } | |
| /** | |
| * Like {@link #consumeAttributeWithDefault(String, String, JType)}, but | |
| * accommodates more complex type signatures. | |
| */ | |
| public String consumeAttributeWithDefault(String name, String defaultValue, | |
| JType[] types) throws UnableToCompleteException { | |
| /* | |
| * TODO(rjrjr) The only reason we need the attribute here is for getParser, | |
| * and getParser only needs it for horrible old BundleAttributeParsers. When | |
| * that dies, this gets much simpler. | |
| */ | |
| XMLAttribute attribute = getAttribute(name); | |
| if (attribute == null) { | |
| if (defaultValue != null) { | |
| designTime.putAttribute(this, name + ".default", defaultValue); | |
| } | |
| return defaultValue; | |
| } | |
| String rawValue = attribute.consumeRawValue(); | |
| AttributeParser parser = getParser(attribute, types); | |
| if (parser == null) { | |
| logger.die(this, "No such attribute %s", name); | |
| } | |
| try { | |
| String value = parser.parse(rawValue); | |
| designTime.putAttribute(this, name, value); | |
| return value; | |
| } catch (UnableToCompleteException e) { | |
| logger.die(this, "Cannot parse attribute %s", name); | |
| throw e; | |
| } | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as a boolean value or | |
| * reference. | |
| * | |
| * @return an expression that will evaluate to a boolean value in the | |
| * generated code, or null if there is no such attribute | |
| * | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeBooleanAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeAttribute(name, getBooleanType()); | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as a boolean value or | |
| * reference. | |
| * | |
| * @param defaultValue value to return if attribute was not set | |
| * @return an expression that will evaluate to a boolean value in the | |
| * generated code, or defaultValue if there is no such attribute | |
| * | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeBooleanAttribute(String name, boolean defaultValue) | |
| throws UnableToCompleteException { | |
| return consumeAttributeWithDefault(name, Boolean.toString(defaultValue), | |
| getBooleanType()); | |
| } | |
| /** | |
| * Consumes the named attribute as a boolean expression. This will not accept | |
| * {field.reference} expressions. Useful for values that must be resolved at | |
| * compile time, such as generated annotation values. | |
| * | |
| * @return {@link Boolean#TRUE}, {@link Boolean#FALSE}, or null if no such | |
| * attribute | |
| * | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public Boolean consumeBooleanConstantAttribute(String name) | |
| throws UnableToCompleteException { | |
| String value = consumeRawAttribute(name); | |
| if (value == null) { | |
| return null; | |
| } | |
| if (value.equals("true") || value.equals("false")) { | |
| return Boolean.valueOf(value); | |
| } | |
| logger.die(this, "%s must be \"true\" or \"false\"", name); | |
| return null; // unreachable | |
| } | |
| /** | |
| * Consumes and returns all child elements. | |
| * | |
| * @throws UnableToCompleteException if extra text nodes are found | |
| */ | |
| public Iterable<XMLElement> consumeChildElements() | |
| throws UnableToCompleteException { | |
| Iterable<XMLElement> rtn = consumeChildElementsNoEmptyCheck(); | |
| assertNoText(); | |
| return rtn; | |
| } | |
| /** | |
| * Consumes and returns all child elements selected by the interpreter. Note | |
| * that text nodes are not elements, and so are not presented for | |
| * interpretation, and are not consumed. | |
| * | |
| * @param interpreter Should return true for any child that should be consumed | |
| * and returned by the consumeChildElements call | |
| * @throws UnableToCompleteException | |
| */ | |
| public Collection<XMLElement> consumeChildElements( | |
| Interpreter<Boolean> interpreter) throws UnableToCompleteException { | |
| List<XMLElement> elements = new ArrayList<XMLElement>(); | |
| List<Node> doomed = new ArrayList<Node>(); | |
| NodeList childNodes = elem.getChildNodes(); | |
| for (int i = 0; i < childNodes.getLength(); ++i) { | |
| Node childNode = childNodes.item(i); | |
| if (childNode.getNodeType() == Node.ELEMENT_NODE) { | |
| XMLElement childElement = provider.get((Element) childNode); | |
| if (interpreter.interpretElement(childElement)) { | |
| elements.add(childElement); | |
| doomed.add(childNode); | |
| } | |
| } | |
| } | |
| for (Node n : doomed) { | |
| elem.removeChild(n); | |
| } | |
| return elements; | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as an ImageResource | |
| * value or reference. | |
| * | |
| * @return an expression that will evaluate to an ImageResource value in the | |
| * generated code, or null if there is no such attribute | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeImageResourceAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeAttribute(name, getImageResourceType()); | |
| } | |
| /** | |
| * Consumes all child elements, and returns an HTML interpretation of them. | |
| * Trailing and leading whitespace is trimmed. | |
| * <p> | |
| * Each element encountered will be passed to the given Interpreter for | |
| * possible replacement. Escaping is performed to allow the returned text to | |
| * serve as a Java string literal used as input to a setInnerHTML call. | |
| * <p> | |
| * This call requires an interpreter to make sense of any special children. | |
| * The odds are you want to use | |
| * {@link com.google.gwt.uibinder.elementparsers.templates.parsers.HtmlInterpreter} | |
| * for an HTML value, or | |
| * {@link com.google.gwt.uibinder.elementparsers.templates.parsers.TextInterpreter} | |
| * for text. | |
| * | |
| * @param interpreter Called for each element, expected to return a string | |
| * replacement for it, or null if it should be left as is | |
| */ | |
| public String consumeInnerHtml(Interpreter<String> interpreter) | |
| throws UnableToCompleteException { | |
| if (interpreter == null) { | |
| throw new NullPointerException("interpreter must not be null"); | |
| } | |
| StringBuffer buf = new StringBuffer(); | |
| GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider); | |
| clearChildren(elem); | |
| return buf.toString().trim(); | |
| } | |
| /** | |
| * Refines {@link #consumeInnerHtml(Interpreter)} to handle | |
| * PostProcessingInterpreter. | |
| */ | |
| public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter) | |
| throws UnableToCompleteException { | |
| String html = consumeInnerHtml((Interpreter<String>) interpreter); | |
| return interpreter.postProcess(html); | |
| } | |
| /** | |
| * Refines {@link #consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter)} to | |
| * handle PostProcessingInterpreter. | |
| */ | |
| public String consumeInnerText(PostProcessingInterpreter<String> interpreter) | |
| throws UnableToCompleteException { | |
| String text = consumeInnerTextEscapedAsHtmlStringLiteral(interpreter); | |
| return interpreter.postProcess(text); | |
| } | |
| /** | |
| * Consumes all child text nodes, and asserts that this element held only | |
| * text. Trailing and leading whitespace is trimmed, and escaped for use as a | |
| * string literal. Notice that HTML entities in the text are also escaped--is | |
| * this a source of errors? | |
| * <p> | |
| * This call requires an interpreter to make sense of any special children. | |
| * The odds are you want to use | |
| * {@link com.google.gwt.uibinder.elementparsers.templates.parsers.TextInterpreter} | |
| * | |
| * @throws UnableToCompleteException If any elements present are not consumed | |
| * by the interpreter | |
| */ | |
| public String consumeInnerTextEscapedAsHtmlStringLiteral( | |
| Interpreter<String> interpreter) throws UnableToCompleteException { | |
| if (interpreter == null) { | |
| throw new NullPointerException("interpreter must not be null"); | |
| } | |
| StringBuffer buf = new StringBuffer(); | |
| GetEscapedInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter, | |
| provider); | |
| // Make sure there are no children left but empty husks | |
| for (XMLElement child : consumeChildElementsNoEmptyCheck()) { | |
| if (child.hasChildNodes() || child.getAttributeCount() > 0) { | |
| logger.die(this, "Illegal child %s in a text-only context. " | |
| + "Perhaps you are trying to use unescaped HTML " | |
| + "where text is required, as in a HasText widget?", child); | |
| } | |
| } | |
| clearChildren(elem); | |
| return buf.toString().trim(); | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as a CSS length value. | |
| * | |
| * @return a (double, Unit) pair literal, an expression that will evaluate to | |
| * such a pair in the generated code, or null if there is no such | |
| * attribute | |
| * | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeLengthAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeAttributeWithDefault(name, null, new JType[]{ | |
| getDoubleType(), getUnitType()}); | |
| } | |
| /** | |
| * Consumes all attributes, and returns a string representing the entire | |
| * opening tag. E.g., "<div able='baker'>" | |
| */ | |
| public String consumeOpeningTag() { | |
| String rtn = getOpeningTag(); | |
| for (int i = getAttributeCount() - 1; i >= 0; i--) { | |
| getAttribute(i).consumeRawValue(); | |
| } | |
| return rtn; | |
| } | |
| /** | |
| * Consumes the named attribute and parses it to an unparsed, unescaped array | |
| * of Strings. The strings in the attribute may be comma or space separated | |
| * (or a mix of both). | |
| * | |
| * @return array of String, empty if the attribute was not set. | |
| */ | |
| public String[] consumeRawArrayAttribute(String name) { | |
| String raw = consumeRawAttribute(name, null); | |
| if (raw == null) { | |
| return EMPTY; | |
| } | |
| return raw.split("[,\\s]+"); | |
| } | |
| /** | |
| * Consumes the given attribute and returns its trimmed value, or null if it | |
| * was unset. The returned string is not escaped. | |
| * | |
| * @param name the attribute's full name (including prefix) | |
| * @return the attribute's value, or "" | |
| */ | |
| public String consumeRawAttribute(String name) { | |
| if (!elem.hasAttribute(name)) { | |
| return null; | |
| } | |
| String value = elem.getAttribute(name); | |
| elem.removeAttribute(name); | |
| return value.trim(); | |
| } | |
| /** | |
| * Consumes the given attribute and returns its trimmed value, or the given | |
| * default value if it was unset. The returned string is not escaped. | |
| * | |
| * @param name the attribute's full name (including prefix) | |
| * @param defaultValue the value to return if the attribute was unset | |
| * @return the attribute's value, or defaultValue | |
| */ | |
| public String consumeRawAttribute(String name, String defaultValue) { | |
| String value = consumeRawAttribute(name); | |
| if (value == null) { | |
| return defaultValue; | |
| } | |
| return value; | |
| } | |
| /** | |
| * Consumes the given required attribute as a literal or field reference. The | |
| * types parameters are required to determine how the value is parsed and | |
| * validated. | |
| * | |
| * @param name the attribute's full name (including prefix) | |
| * @param types the type(s) this attribute is expected to provide | |
| * @return the attribute's value as a Java expression | |
| * @throws UnableToCompleteException on parse failure, or if the attribute is | |
| * empty or unspecified | |
| */ | |
| public String consumeRequiredAttribute(String name, JType... types) | |
| throws UnableToCompleteException { | |
| /* | |
| * TODO(rjrjr) We have to get the attribute to get the parser, and we must | |
| * get the attribute before we consume the value. This nasty subtlety is all | |
| * down to BundleParsers, which we'll hopefully kill off soon. | |
| */ | |
| XMLAttribute attribute = getAttribute(name); | |
| if (attribute == null) { | |
| failRequired(name); | |
| } | |
| AttributeParser parser = getParser(attribute, types); | |
| String rawValue = consumeRequiredRawAttribute(name); | |
| try { | |
| String value = parser.parse(rawValue); | |
| designTime.putAttribute(this, name, value); | |
| return value; | |
| } catch (UnableToCompleteException e) { | |
| logger.die(this, "Cannot parse attribute \"%s\"", name); | |
| throw e; | |
| } | |
| } | |
| /** | |
| * Convenience method for parsing the named required attribute as a double | |
| * value or reference. | |
| * | |
| * @return a double literal, an expression that will evaluate to a double | |
| * value in the generated code | |
| * | |
| * @throws UnableToCompleteException on unparseable value, or if the attribute | |
| * is empty or unspecified | |
| */ | |
| public String consumeRequiredDoubleAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeRequiredAttribute(name, getDoubleType()); | |
| } | |
| /** | |
| * Convenience method for parsing the named required attribute as a integer | |
| * value or reference. | |
| * | |
| * @return a integer literal, an expression that will evaluate to a integer | |
| * value in the generated code | |
| * | |
| * @throws UnableToCompleteException on unparseable value, or if the attribute | |
| * is empty or unspecified | |
| */ | |
| public String consumeRequiredIntAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeRequiredAttribute(name, getIntType()); | |
| } | |
| /** | |
| * Consumes the named attribute, or dies if it is missing. | |
| */ | |
| public String consumeRequiredRawAttribute(String name) | |
| throws UnableToCompleteException { | |
| String value = consumeRawAttribute(name); | |
| if (value == null) { | |
| failRequired(name); | |
| } | |
| return value; | |
| } | |
| /** | |
| * Consumes a single child element, ignoring any text nodes and throwing an | |
| * exception if no child is found, or more than one child element is found. | |
| * | |
| * @throws UnableToCompleteException on no children, or too many | |
| */ | |
| public XMLElement consumeSingleChildElement() | |
| throws UnableToCompleteException { | |
| XMLElement ret = null; | |
| for (XMLElement child : consumeChildElements()) { | |
| if (ret != null) { | |
| logger.die(this, | |
| "Element may only contain a single child element, but " | |
| + "found %s and %s.", ret, child); | |
| } | |
| ret = child; | |
| } | |
| if (ret == null) { | |
| logger.die(this, "Element must have a single child element"); | |
| } | |
| return ret; | |
| } | |
| /** | |
| * Consumes the named attribute and parses it to an array of String | |
| * expressions. The strings in the attribute may be comma or space separated | |
| * (or a mix of both). | |
| * | |
| * @return array of String expressions, empty if the attribute was not set. | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String[] consumeStringArrayAttribute(String name) | |
| throws UnableToCompleteException { | |
| AttributeParser parser = attributeParsers.get(getStringType()); | |
| String[] strings = consumeRawArrayAttribute(name); | |
| for (int i = 0; i < strings.length; i++) { | |
| try { | |
| strings[i] = parser.parse(strings[i]); | |
| } catch (UnableToCompleteException e) { | |
| logger.die(this, "Cannot parse attribute " + name); | |
| throw e; | |
| } | |
| } | |
| designTime.putAttribute(this, name, strings); | |
| return strings; | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as a String value or | |
| * reference. | |
| * | |
| * @return an expression that will evaluate to a String value in the generated | |
| * code, or null if there is no such attribute | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeStringAttribute(String name) | |
| throws UnableToCompleteException { | |
| return consumeAttribute(name, getStringType()); | |
| } | |
| /** | |
| * Convenience method for parsing the named attribute as a String value or | |
| * reference. | |
| * | |
| * @return an expression that will evaluate to a String value in the generated | |
| * code, or the given defaultValue if there is no such attribute | |
| * @throws UnableToCompleteException on unparseable value | |
| */ | |
| public String consumeStringAttribute(String name, String defaultValue) | |
| throws UnableToCompleteException { | |
| return consumeAttributeWithDefault(name, defaultValue, getStringType()); | |
| } | |
| /** | |
| * Returns the unprocessed, unescaped, raw inner text of the receiver. Dies if | |
| * the receiver has non-text children. | |
| * <p> | |
| * You probably want to use | |
| * {@link #consumeInnerTextEscapedAsHtmlStringLiteral} instead. | |
| * | |
| * @return the text | |
| * @throws UnableToCompleteException if it held anything other than text nodes | |
| */ | |
| public String consumeUnescapedInnerText() throws UnableToCompleteException { | |
| final NodeList children = elem.getChildNodes(); | |
| if (children.getLength() < 1) { | |
| return ""; | |
| } | |
| if (children.getLength() > 1 | |
| || Node.TEXT_NODE != children.item(0).getNodeType()) { | |
| logger.die(this, "Element must contain only text"); | |
| } | |
| Text t = (Text) children.item(0); | |
| return t.getTextContent(); | |
| } | |
| /** | |
| * Get the attribute at the given index. If you are consuming attributes, | |
| * remember to traverse them in reverse. | |
| */ | |
| public XMLAttribute getAttribute(int i) { | |
| return new XMLAttribute(XMLElement.this, | |
| (Attr) elem.getAttributes().item(i)); | |
| } | |
| /** | |
| * Get the attribute with the given name. | |
| * | |
| * @return the attribute, or null if there is none of that name | |
| */ | |
| public XMLAttribute getAttribute(String name) { | |
| Attr attr = elem.getAttributeNode(name); | |
| if (attr == null) { | |
| return null; | |
| } | |
| return new XMLAttribute(this, attr); | |
| } | |
| /** | |
| * Returns the number of attributes this element has. | |
| */ | |
| public int getAttributeCount() { | |
| return elem.getAttributes().getLength(); | |
| } | |
| public String getClosingTag() { | |
| if (NO_END_TAG.contains(elem.getTagName())) { | |
| return ""; | |
| } | |
| return String.format("</%s>", elem.getTagName()); | |
| } | |
| /** | |
| * Returns the design time path of this element, in form of indexes from root, | |
| * such as "0/0/1/0". | |
| */ | |
| public String getDesignTimePath() { | |
| return designTime.getPath(elem); | |
| } | |
| /** | |
| * Gets this element's local name (sans namespace prefix). | |
| */ | |
| public String getLocalName() { | |
| return elem.getLocalName(); | |
| } | |
| public Location getLocation() { | |
| return (Location) elem.getUserData(LOCATION_KEY); | |
| } | |
| /** | |
| * Gets this element's namespace URI. | |
| */ | |
| public String getNamespaceUri() { | |
| return elem.getNamespaceURI(); | |
| } | |
| public String getNamespaceUriForAttribute(String fieldName) { | |
| Attr attr = elem.getAttributeNode(fieldName); | |
| return attr.getNamespaceURI(); | |
| } | |
| /** | |
| * Returns the parent element, or null if parent is null or a node type other | |
| * than Element. | |
| */ | |
| public XMLElement getParent() { | |
| Node parent = elem.getParentNode(); | |
| if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) { | |
| return null; | |
| } | |
| return provider.get((Element) parent); | |
| } | |
| public String getPrefix() { | |
| return elem.getPrefix(); | |
| } | |
| /** | |
| * Determines whether the element has a given attribute. | |
| */ | |
| public boolean hasAttribute(String name) { | |
| return elem.hasAttribute(name); | |
| } | |
| public boolean hasChildNodes() { | |
| return elem.hasChildNodes(); | |
| } | |
| public String lookupPrefix(String prefix) { | |
| return elem.lookupPrefix(prefix); | |
| } | |
| public void setAttribute(String name, String value) { | |
| elem.setAttribute(name, value); | |
| } | |
| @Override | |
| public String toString() { | |
| return debugString; | |
| } | |
| private Iterable<XMLElement> consumeChildElementsNoEmptyCheck() { | |
| try { | |
| Iterable<XMLElement> rtn = consumeChildElements(new NoBrainInterpeter<Boolean>( | |
| true)); | |
| return rtn; | |
| } catch (UnableToCompleteException e) { | |
| throw new RuntimeException("Impossible exception", e); | |
| } | |
| } | |
| private void failRequired(String name) throws UnableToCompleteException { | |
| logger.die(this, "Missing required attribute \"%s\"", name); | |
| } | |
| private JType getBooleanType() { | |
| if (booleanType == null) { | |
| try { | |
| booleanType = oracle.parse("boolean"); | |
| } catch (TypeOracleException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| return booleanType; | |
| } | |
| private JType getDoubleType() { | |
| if (doubleType == null) { | |
| try { | |
| doubleType = oracle.parse("double"); | |
| } catch (TypeOracleException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| return doubleType; | |
| } | |
| private JType getImageResourceType() { | |
| if (imageResourceType == null) { | |
| imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); | |
| } | |
| return imageResourceType; | |
| } | |
| private JType getIntType() { | |
| if (intType == null) { | |
| try { | |
| intType = oracle.parse("int"); | |
| } catch (TypeOracleException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| return intType; | |
| } | |
| private String getOpeningTag() { | |
| StringBuilder b = new StringBuilder().append("<").append(elem.getTagName()); | |
| NamedNodeMap attrs = elem.getAttributes(); | |
| for (int i = 0; i < attrs.getLength(); i++) { | |
| Attr attr = (Attr) attrs.item(i); | |
| b.append(String.format(" %s='%s'", attr.getName(), | |
| UiBinderWriter.escapeAttributeText(attr.getValue()))); | |
| } | |
| b.append(">"); | |
| return b.toString(); | |
| } | |
| @SuppressWarnings("deprecation") | |
| private AttributeParser getParser(XMLAttribute xmlAttribute, JType... types) | |
| throws UnableToCompleteException { | |
| AttributeParser rtn = bundleParsers.get(xmlAttribute); | |
| if (rtn == null) { | |
| rtn = attributeParsers.get(types); | |
| } | |
| return rtn; | |
| } | |
| private JType getStringType() { | |
| if (stringType == null) { | |
| stringType = oracle.findType(String.class.getCanonicalName()); | |
| } | |
| return stringType; | |
| } | |
| private JClassType getUnitType() { | |
| return oracle.findType(Unit.class.getCanonicalName()).isEnum(); | |
| } | |
| } |