View Javadoc

1   /*
2    * Copyright 2008 Google Inc.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package com.google.gwt.uibinder.rebind;
17  
18  import com.google.gwt.core.ext.UnableToCompleteException;
19  import com.google.gwt.core.ext.typeinfo.JClassType;
20  import com.google.gwt.core.ext.typeinfo.JType;
21  import com.google.gwt.core.ext.typeinfo.TypeOracle;
22  import com.google.gwt.core.ext.typeinfo.TypeOracleException;
23  import com.google.gwt.dom.client.Style.Unit;
24  import com.google.gwt.resources.client.ImageResource;
25  import com.google.gwt.safehtml.shared.SafeHtml;
26  import com.google.gwt.uibinder.attributeparsers.AttributeParser;
27  import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
28  import com.google.gwt.uibinder.elementparsers.SimpleInterpeter;
29  
30  import org.w3c.dom.Attr;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.NamedNodeMap;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.NodeList;
35  import org.w3c.dom.Text;
36  
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.HashSet;
40  import java.util.List;
41  import java.util.Set;
42  
43  /**
44   * A wrapper for {@link Element} that limits the way parsers can interact with
45   * the XML document, and provides some convenience methods.
46   * <p>
47   * The main function of this wrapper is to ensure that parsers can only read
48   * elements and attributes by 'consuming' them, which removes the given value.
49   * This allows for a natural hierarchy among parsers -- more specific parsers
50   * will run first, and if they consume a value, less-specific parsers will not
51   * see it.
52   */
53  public class XMLElement {
54    /**
55     * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
56     * {@link #consumeChildElements(Interpreter)}.
57     */
58    public interface Interpreter<T> {
59      /**
60       * Given an XMLElement, return its filtered value.
61       * 
62       * @throws UnableToCompleteException on error
63       */
64      T interpretElement(XMLElement elem) throws UnableToCompleteException;
65    }
66  
67    /**
68     * Extends {@link Interpreter} with a method to be called after all elements
69     * have been processed.
70     */
71    public interface PostProcessingInterpreter<T> extends Interpreter<T> {
72      String postProcess(String consumedText) throws UnableToCompleteException;
73    }
74  
75    /**
76     * Represents the source location where the XMLElement was declared.
77     */
78    public static class Location {
79      private final String systemId;
80      private final int lineNumber;
81  
82      public Location(String systemId, int lineNumber) {
83        this.systemId = systemId;
84        this.lineNumber = lineNumber;
85      }
86  
87      public int getLineNumber() {
88        return lineNumber;
89      }
90  
91      public String getSystemId() {
92        return systemId;
93      }
94  
95      /**
96       * For debugging use only.
97       */
98      @Override
99      public String toString() {
100       return systemId + ":" + lineNumber;
101     }
102   }
103 
104   static final String LOCATION_KEY = "gwtLocation";
105 
106   private static final Set<String> NO_END_TAG = new HashSet<String>();
107 
108   private static final String[] EMPTY = new String[]{};
109 
110   private static void clearChildren(Element elem) {
111     // TODO(rjrjr) I'm nearly positive that anywhere this is called
112     // we should instead be calling assertNoBody
113     Node child;
114     while ((child = elem.getFirstChild()) != null) {
115       elem.removeChild(child);
116     }
117   }
118 
119   private final Element elem;
120   private final AttributeParsers attributeParsers;
121   private final TypeOracle oracle;
122 
123   private final MortalLogger logger;
124   private final String debugString;
125 
126   private final DesignTimeUtils designTime;
127 
128   private final XMLElementProvider provider;
129 
130   private JType booleanType;
131   private JType imageResourceType;
132   private JType doubleType;
133   private JType intType;
134   private JType safeHtmlType;
135   private JType stringType;
136 
137   {
138     // from com/google/gxp/compiler/schema/html.xml
139     NO_END_TAG.add("area");
140     NO_END_TAG.add("base");
141     NO_END_TAG.add("basefont");
142     NO_END_TAG.add("br");
143     NO_END_TAG.add("col");
144     NO_END_TAG.add("frame");
145     NO_END_TAG.add("hr");
146     NO_END_TAG.add("img");
147     NO_END_TAG.add("input");
148     NO_END_TAG.add("isindex");
149     NO_END_TAG.add("link");
150     NO_END_TAG.add("meta");
151     NO_END_TAG.add("param");
152     NO_END_TAG.add("wbr");
153   }
154 
155   XMLElement(Element elem, AttributeParsers attributeParsers, TypeOracle oracle,
156       MortalLogger logger, DesignTimeUtils designTime, XMLElementProvider provider) {
157     this.elem = elem;
158     this.attributeParsers = attributeParsers;
159     this.oracle = oracle;
160     this.logger = logger;
161     this.designTime = designTime;
162     this.provider = provider;
163 
164     this.debugString = getOpeningTag();
165   }
166 
167   /**
168    * Ensure that the receiver has no attributes left.
169    * 
170    * @throws UnableToCompleteException if it does
171    */
172   public void assertNoAttributes() throws UnableToCompleteException {
173     int numAtts = getAttributeCount();
174     if (numAtts == 0) {
175       return;
176     }
177 
178     StringBuilder b = new StringBuilder();
179     for (int i = 0; i < numAtts; i++) {
180       if (i > 0) {
181         b.append(", ");
182       }
183       b.append('"').append(getAttribute(i).getName()).append('"');
184     }
185     logger.die(this, "Unexpected attributes: %s", b);
186   }
187 
188   /**
189    * Require that the receiver's body is empty of text and has no child nodes.
190    * 
191    * @throws UnableToCompleteException if it isn't
192    */
193   public void assertNoBody() throws UnableToCompleteException {
194     consumeChildElements(new Interpreter<Boolean>() {
195       public Boolean interpretElement(XMLElement elem) throws UnableToCompleteException {
196         logger.die(elem, "Found unexpected child element");
197         return false; // unreachable
198       }
199     });
200     assertNoText();
201   }
202 
203   /**
204    * Require that the receiver's body is empty of text.
205    * 
206    * @throws UnableToCompleteException if it isn't
207    */
208   public void assertNoText() throws UnableToCompleteException {
209     SimpleInterpeter<String> nullInterpreter = new SimpleInterpeter<String>(null);
210     String s = consumeInnerTextEscapedAsHtmlStringLiteral(nullInterpreter);
211     if (!"".equals(s)) {
212       logger.die(this, "Unexpected text in element: \"%s\"", s);
213     }
214   }
215 
216   /**
217    * Consumes the given attribute as a literal or field reference. The type
218    * parameter is required to determine how the value is parsed and validated.
219    * 
220    * @param name the attribute's full name (including prefix)
221    * @param type the type this attribute is expected to provide
222    * @return the attribute's value as a Java expression, or null if it is not
223    *         set
224    * @throws UnableToCompleteException on parse failure
225    */
226   public String consumeAttribute(String name, JType type) throws UnableToCompleteException {
227     return consumeAttributeWithDefault(name, null, type);
228   }
229 
230   /**
231    * Consumes the given attribute as a literal or field reference. The type
232    * parameter is required to determine how the value is parsed and validated.
233    * 
234    * @param name the attribute's full name (including prefix)
235    * @param defaultValue the value to @return if the attribute was unset
236    * @param type the type this attribute is expected to provide
237    * @return the attribute's value as a Java expression, or the given default if
238    *         it was unset
239    * @throws UnableToCompleteException on parse failure
240    */
241   public String consumeAttributeWithDefault(String name, String defaultValue, JType type)
242       throws UnableToCompleteException {
243     return consumeAttributeWithDefault(name, defaultValue, new JType[]{type});
244   }
245 
246   /**
247    * Like {@link #consumeAttributeWithDefault(String, String, JType)}, but
248    * accommodates more complex type signatures.
249    */
250   public String consumeAttributeWithDefault(String name, String defaultValue, JType... types)
251       throws UnableToCompleteException {
252 
253     if (!hasAttribute(name)) {
254       if (defaultValue != null) {
255         designTime.putAttribute(this, name + ".default", defaultValue);
256       }
257       return defaultValue;
258     }
259 
260     AttributeParser parser = attributeParsers.getParser(types);
261     return consumeAttributeWithParser(name, parser);
262   }
263 
264   /**
265    * Convenience method for parsing the named attribute as a boolean value or
266    * reference.
267    * 
268    * @return an expression that will evaluate to a boolean value in the
269    *         generated code, or null if there is no such attribute
270    * 
271    * @throws UnableToCompleteException on unparseable value
272    */
273   public String consumeBooleanAttribute(String name) throws UnableToCompleteException {
274     return consumeAttribute(name, getBooleanType());
275   }
276 
277   /**
278    * Convenience method for parsing the named attribute as a boolean value or
279    * reference.
280    * 
281    * @param defaultValue value to return if attribute was not set
282    * @return an expression that will evaluate to a boolean value in the
283    *         generated code, or defaultValue if there is no such attribute
284    * 
285    * @throws UnableToCompleteException on unparseable value
286    */
287   public String consumeBooleanAttribute(String name, boolean defaultValue)
288       throws UnableToCompleteException {
289     return consumeAttributeWithDefault(name, Boolean.toString(defaultValue), getBooleanType());
290   }
291 
292   /**
293    * Consumes the named attribute as a boolean expression. This will not accept
294    * {field.reference} expressions. Useful for values that must be resolved at
295    * compile time, such as generated annotation values.
296    * 
297    * @return {@link Boolean#TRUE}, {@link Boolean#FALSE}, or null if no such
298    *         attribute
299    * 
300    * @throws UnableToCompleteException on unparseable value
301    */
302   public Boolean consumeBooleanConstantAttribute(String name) throws UnableToCompleteException {
303     String value = consumeRawAttribute(name);
304     if (value == null) {
305       return null;
306     }
307     if (value.equals("true") || value.equals("false")) {
308       return Boolean.valueOf(value);
309     }
310     logger.die(this, "%s must be \"true\" or \"false\"", name);
311     return null; // unreachable
312   }
313 
314   /**
315    * Consumes and returns all child elements.
316    * 
317    * @throws UnableToCompleteException if extra text nodes are found
318    */
319   public Iterable<XMLElement> consumeChildElements() throws UnableToCompleteException {
320     Iterable<XMLElement> rtn = consumeChildElementsNoEmptyCheck();
321     assertNoText();
322     return rtn;
323   }
324 
325   /**
326    * Consumes and returns all child elements selected by the interpreter. Note
327    * that text nodes are not elements, and so are not presented for
328    * interpretation, and are not consumed.
329    * 
330    * @param interpreter Should return true for any child that should be consumed
331    *          and returned by the consumeChildElements call
332    * @throws UnableToCompleteException
333    */
334   public Collection<XMLElement> consumeChildElements(Interpreter<Boolean> interpreter)
335       throws UnableToCompleteException {
336     List<XMLElement> elements = new ArrayList<XMLElement>();
337     List<Node> doomed = new ArrayList<Node>();
338 
339     NodeList childNodes = elem.getChildNodes();
340     for (int i = 0; i < childNodes.getLength(); ++i) {
341       Node childNode = childNodes.item(i);
342       if (childNode.getNodeType() == Node.ELEMENT_NODE) {
343         XMLElement childElement = provider.get((Element) childNode);
344         if (interpreter.interpretElement(childElement)) {
345           elements.add(childElement);
346           doomed.add(childNode);
347         }
348       }
349     }
350 
351     for (Node n : doomed) {
352       elem.removeChild(n);
353     }
354     return elements;
355   }
356 
357   /**
358    * Convenience method for parsing the named attribute as an ImageResource
359    * value or reference.
360    * 
361    * @return an expression that will evaluate to an ImageResource value in the
362    *         generated code, or null if there is no such attribute
363    * @throws UnableToCompleteException on unparseable value
364    */
365   public String consumeImageResourceAttribute(String name) throws UnableToCompleteException {
366     return consumeAttribute(name, getImageResourceType());
367   }
368 
369   /**
370    * Consumes all child elements, and returns an HTML interpretation of them.
371    * Trailing and leading whitespace is trimmed.
372    * <p>
373    * Each element encountered will be passed to the given Interpreter for
374    * possible replacement. Escaping is performed to allow the returned text to
375    * serve as a Java string literal used as input to a setInnerHTML call.
376    * <p>
377    * This call requires an interpreter to make sense of any special children.
378    * The odds are you want to use
379    * {@link com.google.gwt.uibinder.elementparsers.HtmlInterpreter} for an HTML
380    * value, or {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
381    * for text.
382    * 
383    * @param interpreter Called for each element, expected to return a string
384    *          replacement for it, or null if it should be left as is
385    */
386   public String consumeInnerHtml(Interpreter<String> interpreter) throws UnableToCompleteException {
387     if (interpreter == null) {
388       throw new NullPointerException("interpreter must not be null");
389     }
390     StringBuffer buf = new StringBuffer();
391     GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider);
392 
393     clearChildren(elem);
394     return buf.toString().trim();
395   }
396 
397   /**
398    * Refines {@link #consumeInnerHtml(Interpreter)} to handle
399    * PostProcessingInterpreter.
400    */
401   public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter)
402       throws UnableToCompleteException {
403     String html = consumeInnerHtml((Interpreter<String>) interpreter);
404     return interpreter.postProcess(html);
405   }
406 
407   /**
408    * Refines {@link #consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter)} to
409    * handle PostProcessingInterpreter.
410    */
411   public String consumeInnerText(PostProcessingInterpreter<String> interpreter)
412       throws UnableToCompleteException {
413     String text = consumeInnerTextEscapedAsHtmlStringLiteral(interpreter);
414     return interpreter.postProcess(text);
415   }
416 
417   /**
418    * Consumes all child text nodes, and asserts that this element held only
419    * text. Trailing and leading whitespace is trimmed, and escaped for use as a
420    * string literal. Notice that HTML entities in the text are also escaped
421    * <p>
422    * This call requires an interpreter to make sense of any special children.
423    * The odds are you want to use
424    * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
425    *
426    * @throws UnableToCompleteException If any elements present are not consumed
427    *           by the interpreter
428    */
429   public String consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter<String> interpreter)
430       throws UnableToCompleteException {
431     return consumeInnerTextEscapedAsHtmlStringLiteral(interpreter, true);
432   }
433 
434   /**
435    * Consumes all child text nodes, and asserts that this element held only
436    * text. Trailing and leading whitespace is trimmed, and escaped for use as a
437    * string literal. Notice that HTML entities in the text are NOT escaped
438    * <p>
439    * This call requires an interpreter to make sense of any special children.
440    * The odds are you want to use
441    * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
442    *
443    * @throws UnableToCompleteException If any elements present are not consumed
444    *           by the interpreter
445    */
446   public String consumeInnerTextEscapedAsStringLiteral(Interpreter<String> interpreter)
447       throws UnableToCompleteException {
448     return consumeInnerTextEscapedAsHtmlStringLiteral(interpreter, false);
449   }
450 
451   /**
452    * Convenience method for parsing the named attribute as a CSS length value.
453    * 
454    * @return a (double, Unit) pair literal, an expression that will evaluate to
455    *         such a pair in the generated code, or null if there is no such
456    *         attribute
457    * 
458    * @throws UnableToCompleteException on unparseable value
459    */
460   public String consumeLengthAttribute(String name) throws UnableToCompleteException {
461     return consumeAttributeWithDefault(name, null, new JType[]{getDoubleType(), getUnitType()});
462   }
463 
464   /**
465    * Consumes all attributes, and returns a string representing the entire
466    * opening tag. E.g., "<div able='baker'>"
467    */
468   public String consumeOpeningTag() {
469     String rtn = getOpeningTag();
470 
471     for (int i = getAttributeCount() - 1; i >= 0; i--) {
472       getAttribute(i).consumeRawValue();
473     }
474     return rtn;
475   }
476 
477   /**
478    * Consumes the named attribute and parses it to an unparsed, unescaped array
479    * of Strings. The strings in the attribute may be comma or space separated
480    * (or a mix of both).
481    * 
482    * @return array of String, empty if the attribute was not set.
483    */
484   public String[] consumeRawArrayAttribute(String name) {
485     String raw = consumeRawAttribute(name, null);
486     if (raw == null) {
487       return EMPTY;
488     }
489 
490     return raw.split("[,\\s]+");
491   }
492 
493   /**
494    * Consumes the given attribute and returns its trimmed value, or null if it
495    * was unset. The returned string is not escaped.
496    * 
497    * @param name the attribute's full name (including prefix)
498    * @return the attribute's value, or ""
499    */
500   public String consumeRawAttribute(String name) {
501     if (!elem.hasAttribute(name)) {
502       return null;
503     }
504     String value = elem.getAttribute(name);
505     elem.removeAttribute(name);
506     return value.trim();
507   }
508 
509   /**
510    * Consumes the given attribute and returns its trimmed value, or the given
511    * default value if it was unset. The returned string is not escaped.
512    * 
513    * @param name the attribute's full name (including prefix)
514    * @param defaultValue the value to return if the attribute was unset
515    * @return the attribute's value, or defaultValue
516    */
517   public String consumeRawAttribute(String name, String defaultValue) {
518     String value = consumeRawAttribute(name);
519     if (value == null) {
520       return defaultValue;
521     }
522     return value;
523   }
524 
525   /**
526    * Consumes the given required attribute as a literal or field reference. The
527    * types parameters are required to determine how the value is parsed and
528    * validated.
529    * 
530    * @param name the attribute's full name (including prefix)
531    * @param types the type(s) this attribute is expected to provide
532    * @return the attribute's value as a Java expression
533    * @throws UnableToCompleteException on parse failure, or if the attribute is
534    *           empty or unspecified
535    */
536   public String consumeRequiredAttribute(String name, JType... types)
537       throws UnableToCompleteException {
538     if (!hasAttribute(name)) {
539       failRequired(name);
540     }
541 
542     AttributeParser parser = attributeParsers.getParser(types);
543 
544     String value = parser.parse(this, consumeRequiredRawAttribute(name));
545     designTime.putAttribute(this, name, value);
546     return value;
547   }
548 
549   /**
550    * Convenience method for parsing the named required attribute as a double
551    * value or reference.
552    * 
553    * @return a double literal, an expression that will evaluate to a double
554    *         value in the generated code
555    * 
556    * @throws UnableToCompleteException on unparseable value, or if the attribute
557    *           is empty or unspecified
558    */
559   public String consumeRequiredDoubleAttribute(String name) throws UnableToCompleteException {
560     return consumeRequiredAttribute(name, getDoubleType());
561   }
562 
563   /**
564    * Convenience method for parsing the named required attribute as a integer
565    * value or reference.
566    * 
567    * @return a integer literal, an expression that will evaluate to a integer
568    *         value in the generated code
569    * 
570    * @throws UnableToCompleteException on unparseable value, or if the attribute
571    *           is empty or unspecified
572    */
573   public String consumeRequiredIntAttribute(String name) throws UnableToCompleteException {
574     return consumeRequiredAttribute(name, getIntType());
575   }
576 
577   /**
578    * Consumes the named attribute, or dies if it is missing.
579    */
580   public String consumeRequiredRawAttribute(String name) throws UnableToCompleteException {
581     String value = consumeRawAttribute(name);
582     if (value == null) {
583       failRequired(name);
584     }
585     return value;
586   }
587 
588   /**
589    * Convenience method for parsing the named attribute as a
590    * {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} value or
591    * reference.
592    * 
593    * @return an expression that will evaluate to a
594    *         {@link com.google.gwt.safehtml.shared.SafeHtml SafeHtml} value in
595    *         the generated code, or null if there is no such attribute
596    * @throws UnableToCompleteException on unparseable value
597    */
598   public String consumeSafeHtmlAttribute(String name) throws UnableToCompleteException {
599     return consumeAttribute(name, getSafeHtmlType());
600   }
601 
602   /**
603    * Consumes an attribute as either a SafeUri or a String. Used in HTML
604    * contexts.
605    * 
606    * @return an expression that will evaluate to a SafeUri value in the
607    *         generated code, or null if there is no such attribute
608    * @throws UnableToCompleteException on unparseable value
609    */
610   public String consumeSafeUriOrStringAttribute(String name) throws UnableToCompleteException {
611     return consumeAttributeWithParser(name, attributeParsers.getSafeUriInHtmlParser());
612   }
613 
614   /**
615    * Consumes a single child element, ignoring any text nodes and throwing an
616    * exception if no child is found, or more than one child element is found.
617    * 
618    * @throws UnableToCompleteException on no children, or too many
619    */
620   public XMLElement consumeSingleChildElement() throws UnableToCompleteException {
621     XMLElement ret = null;
622     for (XMLElement child : consumeChildElements()) {
623       if (ret != null) {
624         logger.die(this, "Element may only contain a single child element, but "
625             + "found %s and %s.", ret, child);
626       }
627 
628       ret = child;
629     }
630 
631     if (ret == null) {
632       logger.die(this, "Element must have a single child element");
633     }
634 
635     return ret;
636   }
637 
638   /**
639    * Consumes the named attribute and parses it to an array of String
640    * expressions. The strings in the attribute may be comma or space separated
641    * (or a mix of both).
642    * 
643    * @return array of String expressions, empty if the attribute was not set.
644    * @throws UnableToCompleteException on unparseable value
645    */
646   public String[] consumeStringArrayAttribute(String name) throws UnableToCompleteException {
647     AttributeParser parser = attributeParsers.getParser(getStringType());
648 
649     String[] strings = consumeRawArrayAttribute(name);
650     for (int i = 0; i < strings.length; i++) {
651       strings[i] = parser.parse(this, strings[i]);
652     }
653     designTime.putAttribute(this, name, strings);
654     return strings;
655   }
656 
657   /**
658    * Convenience method for parsing the named attribute as a String value or
659    * reference.
660    * 
661    * @return an expression that will evaluate to a String value in the generated
662    *         code, or null if there is no such attribute
663    * @throws UnableToCompleteException on unparseable value
664    */
665   public String consumeStringAttribute(String name) throws UnableToCompleteException {
666     return consumeAttribute(name, getStringType());
667   }
668 
669   /**
670    * Convenience method for parsing the named attribute as a String value or
671    * reference.
672    * 
673    * @return an expression that will evaluate to a String value in the generated
674    *         code, or the given defaultValue if there is no such attribute
675    * @throws UnableToCompleteException on unparseable value
676    */
677   public String consumeStringAttribute(String name, String defaultValue)
678       throws UnableToCompleteException {
679     return consumeAttributeWithDefault(name, defaultValue, getStringType());
680   }
681 
682   /**
683    * Returns the unprocessed, unescaped, raw inner text of the receiver. Dies if
684    * the receiver has non-text children.
685    * <p>
686    * You probably want to use
687    * {@link #consumeInnerTextEscapedAsHtmlStringLiteral} instead.
688    * 
689    * @return the text
690    * @throws UnableToCompleteException if it held anything other than text nodes
691    */
692   public String consumeUnescapedInnerText() throws UnableToCompleteException {
693     final NodeList children = elem.getChildNodes();
694     if (children.getLength() < 1) {
695       return "";
696     }
697     if (children.getLength() > 1 || Node.TEXT_NODE != children.item(0).getNodeType()) {
698       logger.die(this, "Element must contain only text");
699     }
700     Text t = (Text) children.item(0);
701     return t.getTextContent();
702   }
703 
704   /**
705    * Get the attribute at the given index. If you are consuming attributes,
706    * remember to traverse them in reverse.
707    */
708   public XMLAttribute getAttribute(int i) {
709     return new XMLAttribute(XMLElement.this, (Attr) elem.getAttributes().item(i));
710   }
711 
712   /**
713    * Get the attribute with the given name.
714    * 
715    * @return the attribute, or null if there is none of that name
716    */
717   public XMLAttribute getAttribute(String name) {
718     Attr attr = elem.getAttributeNode(name);
719     if (attr == null) {
720       return null;
721     }
722     return new XMLAttribute(this, attr);
723   }
724 
725   /**
726    * Returns the number of attributes this element has.
727    */
728   public int getAttributeCount() {
729     return elem.getAttributes().getLength();
730   }
731 
732   public String getClosingTag() {
733     if (NO_END_TAG.contains(elem.getTagName())) {
734       return "";
735     }
736     return String.format("</%s>", elem.getTagName());
737   }
738 
739   /**
740    * Returns the design time path of this element, in form of indexes from root,
741    * such as "0/0/1/0".
742    */
743   public String getDesignTimePath() {
744     return designTime.getPath(elem);
745   }
746 
747   /**
748    * Gets this element's local name (sans namespace prefix).
749    */
750   public String getLocalName() {
751     return elem.getLocalName();
752   }
753 
754   public Location getLocation() {
755     return (Location) elem.getUserData(LOCATION_KEY);
756   }
757 
758   /**
759    * Gets this element's namespace URI.
760    */
761   public String getNamespaceUri() {
762     return elem.getNamespaceURI();
763   }
764 
765   /**
766    * Returns the parent element, or null if parent is null or a node type other
767    * than Element.
768    */
769   public XMLElement getParent() {
770     Node parent = elem.getParentNode();
771     if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) {
772       return null;
773     }
774     return provider.get((Element) parent);
775   }
776 
777   public String getPrefix() {
778     return elem.getPrefix();
779   }
780 
781   /**
782    * Determines whether the element has a given attribute.
783    */
784   public boolean hasAttribute(String name) {
785     return elem.hasAttribute(name);
786   }
787 
788   public boolean hasChildNodes() {
789     return elem.hasChildNodes();
790   }
791 
792   public String lookupPrefix(String prefix) {
793     return elem.lookupPrefix(prefix);
794   }
795 
796   public void setAttribute(String name, String value) {
797     elem.setAttribute(name, value);
798   }
799 
800   @Override
801   public String toString() {
802     return debugString;
803   }
804 
805   private String consumeAttributeWithParser(String name, AttributeParser parser)
806       throws UnableToCompleteException {
807     String value = parser.parse(this, consumeRawAttribute(name));
808     designTime.putAttribute(this, name, value);
809     return value;
810   }
811 
812   private Iterable<XMLElement> consumeChildElementsNoEmptyCheck() {
813     try {
814       Iterable<XMLElement> rtn = consumeChildElements(new SimpleInterpeter<Boolean>(true));
815       return rtn;
816     } catch (UnableToCompleteException e) {
817       throw new RuntimeException("Impossible exception", e);
818     }
819   }
820 
821   /**
822    * Consumes all child text nodes, and asserts that this element held only
823    * text. Trailing and leading whitespace is trimmed, and escaped for use as a
824    * string literal. If escapeHtmlEntities is true, HTML Entities are also escaped.
825    * <p>
826    * This call requires an interpreter to make sense of any special children.
827    * The odds are you want to use
828    * {@link com.google.gwt.uibinder.elementparsers.TextInterpreter}
829    *
830    * @throws UnableToCompleteException If any elements present are not consumed
831    *           by the interpreter
832    */
833   private String consumeInnerTextEscapedAsHtmlStringLiteral(Interpreter<String> interpreter,
834                                                            boolean escapeHtmlEntities)
835       throws UnableToCompleteException {
836     if (interpreter == null) {
837       throw new NullPointerException("interpreter must not be null");
838     }
839     StringBuffer buf = new StringBuffer();
840 
841     if (escapeHtmlEntities) {
842       GetInnerTextVisitor.getHtmlEscapedInnerText(elem, buf, interpreter, provider);
843     } else {
844       GetInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter, provider);
845     }
846 
847     // Make sure there are no children left but empty husks
848     for (XMLElement child : consumeChildElementsNoEmptyCheck()) {
849       if (child.hasChildNodes() || child.getAttributeCount() > 0) {
850         logger.die(this, "Illegal child %s in a text-only context. "
851             + "Perhaps you are trying to use unescaped HTML "
852             + "where text is required, as in a HasText widget?", child);
853       }
854     }
855 
856     clearChildren(elem);
857     return buf.toString().trim();
858   }
859 
860   private void failRequired(String name) throws UnableToCompleteException {
861     logger.die(this, "Missing required attribute \"%s\"", name);
862   }
863 
864   private JType getBooleanType() {
865     if (booleanType == null) {
866       try {
867         booleanType = oracle.parse("boolean");
868       } catch (TypeOracleException e) {
869         throw new RuntimeException(e);
870       }
871     }
872     return booleanType;
873   }
874 
875   private JType getDoubleType() {
876     if (doubleType == null) {
877       try {
878         doubleType = oracle.parse("double");
879       } catch (TypeOracleException e) {
880         throw new RuntimeException(e);
881       }
882     }
883     return doubleType;
884   }
885 
886   private JType getImageResourceType() {
887     if (imageResourceType == null) {
888       imageResourceType = oracle.findType(ImageResource.class.getCanonicalName());
889     }
890     return imageResourceType;
891   }
892 
893   private JType getIntType() {
894     if (intType == null) {
895       try {
896         intType = oracle.parse("int");
897       } catch (TypeOracleException e) {
898         throw new RuntimeException(e);
899       }
900     }
901     return intType;
902   }
903 
904   private String getOpeningTag() {
905     StringBuilder b = new StringBuilder().append("<").append(elem.getTagName());
906 
907     NamedNodeMap attrs = elem.getAttributes();
908     for (int i = 0; i < attrs.getLength(); i++) {
909       Attr attr = (Attr) attrs.item(i);
910       b.append(String.format(" %s='%s'", attr.getName(),
911           UiBinderWriter.escapeAttributeText(attr.getValue())));
912     }
913     b.append(">");
914     return b.toString();
915   }
916 
917   private JType getSafeHtmlType() {
918     if (safeHtmlType == null) {
919       safeHtmlType = oracle.findType(SafeHtml.class.getName());
920     }
921     return safeHtmlType;
922   }
923 
924   private JType getStringType() {
925     if (stringType == null) {
926       stringType = oracle.findType(String.class.getCanonicalName());
927     }
928     return stringType;
929   }
930 
931   private JClassType getUnitType() {
932     return oracle.findType(Unit.class.getCanonicalName()).isEnum();
933   }
934   // laaglu
935   public Element getElement() {
936     return elem;
937   }
938   // laaglu
939 }