001: // Copyright 2006, 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.dom;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newLinkedList;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
020: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
021:
022: import java.io.PrintWriter;
023: import java.util.Collections;
024: import java.util.LinkedList;
025: import java.util.List;
026: import java.util.Map;
027:
028: import org.apache.tapestry.ioc.internal.util.Defense;
029: import org.apache.tapestry.ioc.internal.util.InternalUtils;
030:
031: /**
032: * An element that will render with a begin tag and attributes, a body, and an end tag. Also acts as
033: * a factory for enclosed Element, Text and Comment nodes. TODO: Support for CDATA nodes. Do we need
034: * Entity nodes?
035: */
036: public final class Element extends Node {
037: private final String _name;
038:
039: private Map<String, String> _attributes;
040:
041: private Element _parent;
042:
043: private final Document _document;
044:
045: Element(Document container, String name) {
046: super (container);
047:
048: _document = container;
049: _name = name;
050: }
051:
052: Element(Element parent, String name) {
053: super (parent);
054:
055: _parent = parent;
056: _name = name;
057:
058: _document = parent.getDocument();
059: }
060:
061: public Document getDocument() {
062: return _document;
063: }
064:
065: /**
066: * Returns the containing element for this element. This will be null for the root element of a
067: * document.
068: */
069: public Element getParent() {
070: return _parent;
071: }
072:
073: /**
074: * Adds an attribute to the element, but only if the attribute name does not already exist.
075: *
076: * @param name
077: * the name of the attribute to add
078: * @param value
079: * the value for the attribute. A value of null is allowed, and no attribute will be
080: * added to the element.
081: */
082: public void attribute(String name, String value) {
083: notBlank(name, "name");
084:
085: if (value == null)
086: return;
087:
088: if (_attributes == null)
089: _attributes = newMap();
090:
091: if (!_attributes.containsKey(name))
092: _attributes.put(name, value);
093: }
094:
095: /**
096: * Convienience for invoking {@link #attribute(String, String)} multiple times.
097: *
098: * @param namesAndValues
099: * alternating attribute names and attribute values
100: */
101: public void attributes(String... namesAndValues) {
102: int i = 0;
103: while (i < namesAndValues.length) {
104: String name = namesAndValues[i++];
105: String value = namesAndValues[i++];
106:
107: attribute(name, value);
108: }
109: }
110:
111: /**
112: * Forces changes to a number of attributes. The new attributes <em>overwrite</em> previous
113: * values.
114: */
115: public void forceAttributes(String... namesAndValues) {
116: if (_attributes == null)
117: _attributes = newMap();
118:
119: int i = 0;
120:
121: while (i < namesAndValues.length) {
122: String name = namesAndValues[i++];
123: String value = namesAndValues[i++];
124:
125: if (value == null) {
126: _attributes.remove(name);
127: continue;
128: }
129:
130: _attributes.put(name, value);
131: }
132: }
133:
134: /**
135: * Creates and returns a new Element node as a child of this node.
136: *
137: * @param name
138: * the name of the element to create
139: * @param namesAndValues
140: * alternating attribute names and attribute values
141: */
142: public Element element(String name, String... namesAndValues) {
143: notBlank(name, "name");
144:
145: Element child = newChild(new Element(this , name));
146:
147: child.attributes(namesAndValues);
148:
149: return child;
150: }
151:
152: public Element elementAt(int index, String name,
153: String... namesAndValues) {
154: notBlank(name, "name");
155:
156: Element child = new Element(this , name);
157: child.attributes(namesAndValues);
158:
159: insertChildAt(index, child);
160:
161: return child;
162: }
163:
164: public void comment(String text) {
165: newChild(new Comment(this , text));
166: }
167:
168: public void raw(String text) {
169: newChild(new Raw(this , text));
170: }
171:
172: /**
173: * Adds and returns a new text node (the text node is returned so that
174: * {@link Text#write(String)} or [@link {@link Text#writef(String, Object[])} may be invoked .
175: *
176: * @param text
177: * initial text for the node
178: * @return the new Text node
179: */
180: public Text text(String text) {
181: return newChild(new Text(this , _document, text));
182: }
183:
184: private <T extends Node> T newChild(T child) {
185: addChild(child);
186:
187: return child;
188: }
189:
190: @Override
191: public void toMarkup(PrintWriter writer) {
192: writer.printf("<%s", _name);
193:
194: if (_attributes != null) {
195: List<String> keys = newList(_attributes.keySet());
196: Collections.sort(keys);
197:
198: for (String key : keys) {
199: String value = _attributes.get(key);
200:
201: // TODO: URL encoding of attributes!
202:
203: writer.printf(" %s=\"%s\"", key, value);
204: }
205: }
206:
207: EndTagStyle style = _document.getMarkupModel().getEndTagStyle(
208: _name);
209:
210: boolean hasChildren = hasChildren();
211:
212: String close = (!hasChildren && style == EndTagStyle.ABBREVIATE) ? "/>"
213: : ">";
214:
215: writer.print(close);
216:
217: if (hasChildren)
218: writeChildMarkup(writer);
219:
220: // Dangerous -- perhaps it should be an error for a tag of type OMIT to even have children!
221: // We'll certainly be writing out unbalanced markup in that case.
222:
223: if (style == EndTagStyle.OMIT)
224: return;
225:
226: if (hasChildren || style == EndTagStyle.REQUIRE)
227: writer.printf("</%s>", _name);
228: }
229:
230: /**
231: * Tries to find an element under this element (including itself) whose id is specified.
232: * Performs a width-first search of the document tree.
233: *
234: * @param id
235: * the value of the id attribute of the element being looked for
236: * @return the element if found. null if not found.
237: */
238: public Element getElementById(String id) {
239: Defense.notNull(id, "id");
240:
241: LinkedList<Element> queue = newLinkedList();
242:
243: queue.add(this );
244:
245: while (!queue.isEmpty()) {
246: Element e = queue.removeFirst();
247:
248: String elementId = e.getAttribute("id");
249:
250: if (id.equals(elementId))
251: return e;
252:
253: for (Node n : e.getChildren()) {
254: Element child = n.asElement();
255:
256: if (child != null)
257: queue.addLast(child);
258: }
259: }
260:
261: // Exhausted the entire tree
262:
263: return null;
264: }
265:
266: /**
267: * Searchs for a child element with a particular name below this element. The path parameter is
268: * a slash separated series of element names.
269: *
270: * @param path
271: * @return
272: */
273: public Element find(String path) {
274: notBlank(path, "path");
275:
276: Element search = this ;
277:
278: for (String name : path.split("/")) {
279: search = search.findChildWithElementName(name);
280:
281: if (search == null)
282: break;
283: }
284:
285: return search;
286: }
287:
288: private Element findChildWithElementName(String name) {
289: for (Node node : getChildren()) {
290: Element child = node.asElement();
291:
292: if (child != null && child.getName().equals(name))
293: return child;
294: }
295:
296: // Not found.
297:
298: return null;
299: }
300:
301: public String getAttribute(String attributeName) {
302: return InternalUtils.get(_attributes, attributeName);
303: }
304:
305: public String getName() {
306: return _name;
307: }
308:
309: /** All other implementations of Node return null except this one. */
310: @Override
311: Element asElement() {
312: return this;
313: }
314: }
|