/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 freemarker.template.utility;

import java.util.HashMap;
import java.util.List;

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 freemarker.template.SimpleHash;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateSequenceModel;

A convenient wrapper class for wrapping a Node in the W3C DOM API.
/** * A convenient wrapper class for wrapping a Node in the W3C DOM API. */
public class DOMNodeModel implements TemplateHashModel { static private HashMap equivalenceTable = new HashMap(); static { equivalenceTable.put("*", "children"); equivalenceTable.put("@*", "attributes"); } private Node node; private HashMap cache = new HashMap(); public DOMNodeModel(Node node) { this.node = node; } public TemplateModel get(String key) throws TemplateModelException { TemplateModel result = null; if (equivalenceTable.containsKey(key)) { key = (String) equivalenceTable.get(key); } if (cache.containsKey(key)) { result = (TemplateModel) cache.get(key); } if (result == null) { if ("attributes".equals(key)) { NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { SimpleHash hash = new SimpleHash(); for (int i = 0; i < attributes.getLength(); i++) { Attr att = (Attr) attributes.item(i); hash.put(att.getName(), att.getValue()); } result = hash; } } else if (key.charAt(0) == '@') { if (node instanceof Element) { String attValue = ((Element) node).getAttribute(key.substring(1)); result = new SimpleScalar(attValue); } else { throw new TemplateModelException("Trying to get an attribute value for a non-element node"); } } else if ("is_element".equals(key)) { result = (node instanceof Element) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; } else if ("is_text".equals(key)) { result = (node instanceof Text) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; } else if ("name".equals(key)) { result = new SimpleScalar(node.getNodeName()); } else if ("children".equals(key)) { result = new NodeListTM(node.getChildNodes()); } else if ("parent".equals(key)) { Node parent = node.getParentNode(); result = (parent == null) ? null : new DOMNodeModel(parent); } else if ("ancestorByName".equals(key)) { result = new AncestorByName(); } else if ("nextSibling".equals(key)) { Node next = node.getNextSibling(); result = (next == null) ? null : new DOMNodeModel(next); } else if ("previousSibling".equals(key)) { Node previous = node.getPreviousSibling(); result = (previous == null) ? null : new DOMNodeModel(previous); } else if ("nextSiblingElement".equals(key)) { Node next = nextSiblingElement(node); result = (next == null) ? null : new DOMNodeModel(next); } else if ("previousSiblingElement".equals(key)) { Node previous = previousSiblingElement(node); result = (previous == null) ? null : new DOMNodeModel(previous); } else if ("nextElement".equals(key)) { Node next = nextElement(node); result = (next == null) ? null : new DOMNodeModel(next); } else if ("previousElement".equals(key)) { Node previous = previousElement(node); result = (previous == null) ? null : new DOMNodeModel(previous); } else if ("text".equals(key)) { result = new SimpleScalar(getText(node)); } cache.put(key, result); } return result; } public boolean isEmpty() { return false; } static private String getText(Node node) { String result = ""; if (node instanceof Text) { result = ((Text) node).getData(); } else if (node instanceof Element) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { result += getText(children.item(i)); } } return result; } static private Element nextSiblingElement(Node node) { Node next = node; while (next != null) { next = next.getNextSibling(); if (next instanceof Element) { return (Element) next; } } return null; } static private Element previousSiblingElement(Node node) { Node previous = node; while (previous != null) { previous = previous.getPreviousSibling(); if (previous instanceof Element) { return (Element) previous; } } return null; } static private Element nextElement(Node node) { if (node.hasChildNodes()) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element) { return (Element) child; } } } Element nextSiblingElement = nextSiblingElement(node); if (nextSiblingElement != null) { return nextSiblingElement; } Node parent = node.getParentNode(); while (parent instanceof Element) { Element next = nextSiblingElement(parent); if (next != null) { return next; } parent = parent.getParentNode(); } return null; } static private Element previousElement(Node node) { Element result = previousSiblingElement(node); if (result != null) { return result; } Node parent = node.getParentNode(); if (parent instanceof Element) { return (Element) parent; } return null; } void setParent(DOMNodeModel parent) { if (parent != null) { cache.put("parent", parent); } } String getNodeName() { return node.getNodeName(); } class AncestorByName implements TemplateMethodModel { public Object exec(List arguments) throws TemplateModelException { if (arguments.size() != 1) { throw new TemplateModelException("Expecting exactly one string argument here"); } String nodeName = (String) arguments.get(0); DOMNodeModel ancestor = (DOMNodeModel) DOMNodeModel.this.get("parent"); while (ancestor != null) { if (nodeName.equals(ancestor.getNodeName())) { return ancestor; } ancestor = (DOMNodeModel) ancestor.get("parent"); } return null; } } class NodeListTM implements TemplateSequenceModel, TemplateMethodModel { private NodeList nodeList; private TemplateModel[] nodes; NodeListTM(NodeList nodeList) { this.nodeList = nodeList; nodes = new TemplateModel[nodeList.getLength()]; } public TemplateModel get(int index) { DOMNodeModel result = (DOMNodeModel) nodes[index]; if (result == null) { result = new DOMNodeModel(nodeList.item(index)); nodes[index] = result; result.setParent(DOMNodeModel.this); } return result; } public int size() { return nodes.length; } public Object exec(List arguments) throws TemplateModelException { if (arguments.size() != 1) { throw new TemplateModelException("Expecting exactly one string argument here"); } if (!(node instanceof Element)) { throw new TemplateModelException("Expecting element here."); } Element elem = (Element) node; return new NodeListTM(elem.getElementsByTagName((String) arguments.get(0))); } } }