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.ext.dom;
054:
055: import freemarker.template.*;
056: import freemarker.template.utility.Collections12;
057: import freemarker.template.utility.UndeclaredThrowableException;
058: import freemarker.cache.TemplateCache;
059: import freemarker.core.CustomAttribute;
060: import freemarker.core.Environment;
061: import org.jaxen.*;
062: import org.jaxen.dom.DocumentNavigator;
063: import org.w3c.dom.Document;
064: import org.xml.sax.EntityResolver;
065: import org.xml.sax.InputSource;
066: import org.xml.sax.SAXException;
067:
068: import java.io.IOException;
069: import java.io.StringReader;
070: import java.io.StringWriter;
071: import java.util.*;
072:
073: import javax.xml.parsers.DocumentBuilder;
074: import javax.xml.parsers.DocumentBuilderFactory;
075:
076: /**
077: * @author Jonathan Revusky
078: * @version $Id: JaxenXPathSupport.java,v 1.30 2003/12/20 01:17:30 ddekany Exp $
079: */
080: class JaxenXPathSupport implements XPathSupport {
081:
082: private static final CustomAttribute cache = new CustomAttribute(
083: CustomAttribute.SCOPE_TEMPLATE) {
084: protected Object create() {
085: return new HashMap();
086: }
087: };
088:
089: private final static ArrayList EMPTY_ARRAYLIST = new ArrayList();
090:
091: public TemplateModel executeQuery(Object context, String xpathQuery)
092: throws TemplateModelException {
093: try {
094: BaseXPath xpath;
095: Map xpathCache = (Map) cache.get();
096: synchronized (xpathCache) {
097: xpath = (BaseXPath) xpathCache.get(xpathQuery);
098: if (xpath == null) {
099: xpath = new BaseXPath(xpathQuery, fmDomNavigator);
100: xpath.setNamespaceContext(customNamespaceContext);
101: xpath.setFunctionContext(fmFunctionContext);
102: xpath.setVariableContext(fmVariableContext);
103: xpathCache.put(xpathQuery, xpath);
104: }
105: }
106: List result = xpath.selectNodes(context != null ? context
107: : EMPTY_ARRAYLIST);
108: if (result.size() == 1) {
109: return ObjectWrapper.DEFAULT_WRAPPER
110: .wrap(result.get(0));
111: }
112: NodeListModel nlm = new NodeListModel(result, null);
113: nlm.xpathSupport = this ;
114: return nlm;
115: } catch (UndeclaredThrowableException e) {
116: Throwable t = e.getUndeclaredThrowable();
117: if (t instanceof TemplateModelException) {
118: throw (TemplateModelException) t;
119: }
120: throw e;
121: } catch (JaxenException je) {
122: throw new TemplateModelException(je);
123: }
124: }
125:
126: static private final NamespaceContext customNamespaceContext = new NamespaceContext() {
127:
128: public String translateNamespacePrefixToUri(String prefix) {
129: if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
130: return Environment.getCurrentEnvironment()
131: .getDefaultNS();
132: }
133: return Environment.getCurrentEnvironment()
134: .getNamespaceForPrefix(prefix);
135: }
136: };
137:
138: private static final VariableContext fmVariableContext = new VariableContext() {
139: public Object getVariableValue(String namespaceURI,
140: String prefix, String localName)
141: throws UnresolvableException {
142: try {
143: TemplateModel model = Environment
144: .getCurrentEnvironment().getVariable(localName);
145: if (model == null) {
146: throw new UnresolvableException("Variable "
147: + localName + " not found.");
148: }
149: if (model instanceof TemplateScalarModel) {
150: return ((TemplateScalarModel) model).getAsString();
151: }
152: if (model instanceof TemplateNumberModel) {
153: return ((TemplateNumberModel) model).getAsNumber();
154: }
155: if (model instanceof TemplateDateModel) {
156: return ((TemplateDateModel) model).getAsDate();
157: }
158: if (model instanceof TemplateBooleanModel) {
159: return ((TemplateBooleanModel) model)
160: .getAsBoolean() ? Boolean.TRUE
161: : Boolean.FALSE;
162: }
163: } catch (TemplateModelException e) {
164: throw new UndeclaredThrowableException(e);
165: }
166: throw new UnresolvableException("Variable " + localName
167: + " is not a string, number, date, or boolean");
168: }
169: };
170:
171: private static final FunctionContext fmFunctionContext = new XPathFunctionContext() {
172: public Function getFunction(String namespaceURI, String prefix,
173: String localName) throws UnresolvableException {
174: try {
175: return super .getFunction(namespaceURI, prefix,
176: localName);
177: } catch (UnresolvableException e) {
178: return super .getFunction(null, null, localName);
179: }
180: }
181: };
182:
183: private static final CustomAttribute cachedTree = new CustomAttribute(
184: CustomAttribute.SCOPE_TEMPLATE);
185:
186: private static final Navigator fmDomNavigator = new DocumentNavigator() {
187: public Object getDocument(String uri)
188: throws FunctionCallException {
189: try {
190: Template raw = getTemplate(uri);
191: Document doc = (Document) cachedTree.get(raw);
192: if (doc == null) {
193: DocumentBuilderFactory factory = DocumentBuilderFactory
194: .newInstance();
195: factory.setNamespaceAware(true);
196: DocumentBuilder builder = factory
197: .newDocumentBuilder();
198: FmEntityResolver er = new FmEntityResolver();
199: builder.setEntityResolver(er);
200: doc = builder.parse(createInputSource(null, raw));
201: // If the entity resolver got called 0 times, the document
202: // is standalone, so we can safely cache it
203: if (er.getCallCount() == 0) {
204: cachedTree.set(doc, raw);
205: }
206: }
207: return doc;
208: } catch (Exception e) {
209: throw new FunctionCallException(
210: "Failed to parse document for URI: " + uri, e);
211: }
212: }
213: };
214:
215: static Template getTemplate(String systemId) throws IOException {
216: Environment env = Environment.getCurrentEnvironment();
217: String encoding = env.getTemplate().getEncoding();
218: if (encoding == null) {
219: encoding = env.getConfiguration().getEncoding(
220: env.getLocale());
221: }
222: String templatePath = env.getTemplate().getName();
223: int lastSlash = templatePath.lastIndexOf('/');
224: templatePath = lastSlash == -1 ? "" : templatePath.substring(0,
225: lastSlash + 1);
226: systemId = TemplateCache.getFullTemplatePath(env, templatePath,
227: systemId);
228: Template raw = env.getConfiguration().getTemplate(systemId,
229: env.getLocale(), encoding, false);
230: return raw;
231: }
232:
233: private static InputSource createInputSource(String publicId,
234: Template raw) throws IOException, SAXException {
235: StringWriter sw = new StringWriter();
236: try {
237: raw.process(Collections12.EMPTY_MAP, sw);
238: } catch (TemplateException e) {
239: throw new SAXException(e);
240: }
241: InputSource is = new InputSource();
242: is.setPublicId(publicId);
243: is.setSystemId(raw.getName());
244: is.setCharacterStream(new StringReader(sw.toString()));
245: return is;
246: }
247:
248: private static class FmEntityResolver implements EntityResolver {
249: private int callCount = 0;
250:
251: public InputSource resolveEntity(String publicId,
252: String systemId) throws SAXException, IOException {
253: ++callCount;
254: return createInputSource(publicId, getTemplate(systemId));
255: }
256:
257: int getCallCount() {
258: return callCount;
259: }
260: };
261: }
|