001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.template.utility;
054:
055: import freemarker.template.*;
056: import org.w3c.dom.*;
057: import java.util.*;
058:
059: /**
060: * A convenient wrapper class for wrapping a Node in the W3C DOM API.
061: * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
062: */
063:
064: public class DOMNodeModel implements TemplateHashModel {
065:
066: static private HashMap equivalenceTable = new HashMap();
067: static {
068: equivalenceTable.put("*", "children");
069: equivalenceTable.put("@*", "attributes");
070: }
071:
072: private Node node;
073: private HashMap cache = new HashMap();
074:
075: public DOMNodeModel(Node node) {
076: this .node = node;
077: }
078:
079: public TemplateModel get(String key) throws TemplateModelException {
080: TemplateModel result = null;
081: if (equivalenceTable.containsKey(key)) {
082: key = (String) equivalenceTable.get(key);
083: }
084: if (cache.containsKey(key)) {
085: result = (TemplateModel) cache.get(key);
086: }
087: if (result == null) {
088: if ("attributes".equals(key)) {
089: NamedNodeMap attributes = node.getAttributes();
090: if (attributes != null) {
091: SimpleHash hash = new SimpleHash();
092: for (int i = 0; i < attributes.getLength(); i++) {
093: Attr att = (Attr) attributes.item(i);
094: hash.put(att.getName(), att.getValue());
095: }
096: result = hash;
097: }
098: } else if (key.charAt(0) == '@') {
099: if (node instanceof Element) {
100: String attValue = ((Element) node).getAttribute(key
101: .substring(1));
102: result = new SimpleScalar(attValue);
103: } else {
104: throw new TemplateModelException(
105: "Trying to get an attribute value for a non-element node");
106: }
107: } else if ("is_element".equals(key)) {
108: result = (node instanceof Element) ? TemplateBooleanModel.TRUE
109: : TemplateBooleanModel.FALSE;
110: } else if ("is_text".equals(key)) {
111: result = (node instanceof Text) ? TemplateBooleanModel.TRUE
112: : TemplateBooleanModel.FALSE;
113: } else if ("name".equals(key)) {
114: result = new SimpleScalar(node.getNodeName());
115: } else if ("children".equals(key)) {
116: result = new NodeListTM(node.getChildNodes());
117: } else if ("parent".equals(key)) {
118: Node parent = node.getParentNode();
119: result = (parent == null) ? null : new DOMNodeModel(
120: parent);
121: } else if ("ancestorByName".equals(key)) {
122: result = new AncestorByName();
123: } else if ("nextSibling".equals(key)) {
124: Node next = node.getNextSibling();
125: result = (next == null) ? null : new DOMNodeModel(next);
126: } else if ("previousSibling".equals(key)) {
127: Node previous = node.getPreviousSibling();
128: result = (previous == null) ? null : new DOMNodeModel(
129: previous);
130: } else if ("nextSiblingElement".equals(key)) {
131: Node next = nextSiblingElement(node);
132: result = (next == null) ? null : new DOMNodeModel(next);
133: } else if ("previousSiblingElement".equals(key)) {
134: Node previous = previousSiblingElement(node);
135: result = (previous == null) ? null : new DOMNodeModel(
136: previous);
137: } else if ("nextElement".equals(key)) {
138: Node next = nextElement(node);
139: result = (next == null) ? null : new DOMNodeModel(next);
140: } else if ("previousElement".equals(key)) {
141: Node previous = previousElement(node);
142: result = (previous == null) ? null : new DOMNodeModel(
143: previous);
144: } else if ("text".equals(key)) {
145: result = new SimpleScalar(getText(node));
146: }
147: cache.put(key, result);
148: }
149: return result;
150: }
151:
152: public boolean isEmpty() {
153: return false;
154: }
155:
156: static private String getText(Node node) {
157: String result = "";
158: if (node instanceof Text) {
159: result = ((Text) node).getData();
160: } else if (node instanceof Element) {
161: NodeList children = node.getChildNodes();
162: for (int i = 0; i < children.getLength(); i++) {
163: result += getText(children.item(i));
164: }
165: }
166: return result;
167: }
168:
169: static private Element nextSiblingElement(Node node) {
170: Node next = node;
171: while (next != null) {
172: next = next.getNextSibling();
173: if (next instanceof Element) {
174: return (Element) next;
175: }
176: }
177: return null;
178: }
179:
180: static private Element previousSiblingElement(Node node) {
181: Node previous = node;
182: while (previous != null) {
183: previous = previous.getPreviousSibling();
184: if (previous instanceof Element) {
185: return (Element) previous;
186: }
187: }
188: return null;
189: }
190:
191: static private Element nextElement(Node node) {
192: if (node.hasChildNodes()) {
193: NodeList children = node.getChildNodes();
194: for (int i = 0; i < children.getLength(); i++) {
195: Node child = children.item(i);
196: if (child instanceof Element) {
197: return (Element) child;
198: }
199: }
200: }
201: Element nextSiblingElement = nextSiblingElement(node);
202: if (nextSiblingElement != null) {
203: return nextSiblingElement;
204: }
205: Node parent = node.getParentNode();
206: while (parent instanceof Element) {
207: Element next = nextSiblingElement(parent);
208: if (next != null) {
209: return next;
210: }
211: parent = parent.getParentNode();
212: }
213: return null;
214: }
215:
216: static private Element previousElement(Node node) {
217: Element result = previousSiblingElement(node);
218: if (result != null) {
219: return result;
220: }
221: Node parent = node.getParentNode();
222: if (parent instanceof Element) {
223: return (Element) parent;
224: }
225: return null;
226: }
227:
228: void setParent(DOMNodeModel parent) {
229: if (parent != null) {
230: cache.put("parent", parent);
231: }
232: }
233:
234: String getNodeName() {
235: return node.getNodeName();
236: }
237:
238: class AncestorByName implements TemplateMethodModel {
239: public Object exec(List arguments)
240: throws TemplateModelException {
241: if (arguments.size() != 1) {
242: throw new TemplateModelException(
243: "Expecting exactly one string argument here");
244: }
245: String nodeName = (String) arguments.get(0);
246: DOMNodeModel ancestor = (DOMNodeModel) DOMNodeModel.this
247: .get("parent");
248: while (ancestor != null) {
249: if (nodeName.equals(ancestor.getNodeName())) {
250: return ancestor;
251: }
252: ancestor = (DOMNodeModel) ancestor.get("parent");
253: }
254: return null;
255: }
256: }
257:
258: class NodeListTM implements TemplateSequenceModel,
259: TemplateMethodModel {
260:
261: private NodeList nodeList;
262: private TemplateModel[] nodes;
263:
264: NodeListTM(NodeList nodeList) {
265: this .nodeList = nodeList;
266: nodes = new TemplateModel[nodeList.getLength()];
267: }
268:
269: public TemplateModel get(int index) {
270: DOMNodeModel result = (DOMNodeModel) nodes[index];
271: if (result == null) {
272: result = new DOMNodeModel(nodeList.item(index));
273: nodes[index] = result;
274: result.setParent(DOMNodeModel.this );
275: }
276: return result;
277: }
278:
279: public int size() {
280: return nodes.length;
281: }
282:
283: public Object exec(List arguments)
284: throws TemplateModelException {
285: if (arguments.size() != 1) {
286: throw new TemplateModelException(
287: "Expecting exactly one string argument here");
288: }
289: if (!(node instanceof Element)) {
290: throw new TemplateModelException(
291: "Expecting element here.");
292: }
293: Element elem = (Element) node;
294: return new NodeListTM(elem
295: .getElementsByTagName((String) arguments.get(0)));
296: }
297: }
298: }
|