1 52 53 package freemarker.ext.dom; 54 55 import freemarker.template.*; 56 import freemarker.template.utility.Collections12; 57 import freemarker.template.utility.UndeclaredThrowableException; 58 import freemarker.cache.TemplateCache; 59 import freemarker.core.CustomAttribute; 60 import freemarker.core.Environment; 61 import org.jaxen.*; 62 import org.jaxen.dom.DocumentNavigator; 63 import org.w3c.dom.Document ; 64 import org.xml.sax.EntityResolver ; 65 import org.xml.sax.InputSource ; 66 import org.xml.sax.SAXException ; 67 68 import java.io.IOException ; 69 import java.io.StringReader ; 70 import java.io.StringWriter ; 71 import java.util.*; 72 73 import javax.xml.parsers.DocumentBuilder ; 74 import javax.xml.parsers.DocumentBuilderFactory ; 75 76 77 81 class JaxenXPathSupport implements XPathSupport { 82 83 private static final CustomAttribute cache = 84 new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE) { 85 protected Object create() { 86 return new HashMap(); 87 } 88 }; 89 90 private final static ArrayList EMPTY_ARRAYLIST = new ArrayList(); 91 92 public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException { 93 try { 94 BaseXPath xpath; 95 Map xpathCache = (Map)cache.get(); 96 synchronized(xpathCache) { 97 xpath = (BaseXPath) xpathCache.get(xpathQuery); 98 if (xpath == null) { 99 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 : EMPTY_ARRAYLIST); 107 if (result.size() == 1) { 108 return ObjectWrapper.DEFAULT_WRAPPER.wrap(result.get(0)); 109 } 110 NodeListModel nlm = new NodeListModel(result, null); 111 nlm.xpathSupport = this; 112 return nlm; 113 } catch (UndeclaredThrowableException e) { 114 Throwable t = e.getUndeclaredThrowable(); 115 if(t instanceof TemplateModelException) { 116 throw (TemplateModelException)t; 117 } 118 throw e; 119 } catch (JaxenException je) { 120 throw new TemplateModelException(je); 121 } 122 } 123 124 static private final NamespaceContext customNamespaceContext = new NamespaceContext() { 125 126 public String translateNamespacePrefixToUri(String prefix) { 127 if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) { 128 return Environment.getCurrentEnvironment().getDefaultNS(); 129 } 130 return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix); 131 } 132 }; 133 134 private static final VariableContext fmVariableContext = new VariableContext() { 135 public Object getVariableValue(String namespaceURI, String prefix, String localName) 136 throws 137 UnresolvableException 138 { 139 try { 140 TemplateModel model = Environment.getCurrentEnvironment().getVariable(localName); 141 if(model == null) { 142 throw new UnresolvableException("Variable " + localName + " not found."); 143 } 144 if(model instanceof TemplateScalarModel) { 145 return ((TemplateScalarModel)model).getAsString(); 146 } 147 if(model instanceof TemplateNumberModel) { 148 return ((TemplateNumberModel)model).getAsNumber(); 149 } 150 if(model instanceof TemplateDateModel) { 151 return ((TemplateDateModel)model).getAsDate(); 152 } 153 if(model instanceof TemplateBooleanModel) { 154 return ((TemplateBooleanModel)model).getAsBoolean() ? Boolean.TRUE : Boolean.FALSE; 155 } 156 } 157 catch(TemplateModelException e) { 158 throw new UndeclaredThrowableException(e); 159 } 160 throw new UnresolvableException("Variable " + localName + " is not a string, number, date, or boolean"); 161 } 162 }; 163 164 private static final FunctionContext fmFunctionContext = new XPathFunctionContext() { 165 public Function getFunction(String namespaceURI, String prefix, String localName) 166 throws UnresolvableException { 167 try { 168 return super.getFunction(namespaceURI, prefix, localName); 169 } 170 catch(UnresolvableException e) { 171 return super.getFunction(null, null, localName); 172 } 173 } 174 }; 175 176 private static final CustomAttribute cachedTree = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE); 177 178 private static final Navigator fmDomNavigator = new DocumentNavigator() { 179 public Object getDocument(String uri) throws FunctionCallException 180 { 181 try 182 { 183 Template raw = getTemplate(uri); 184 Document doc = (Document )cachedTree.get(raw); 185 if(doc == null) { 186 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 187 factory.setNamespaceAware(true); 188 DocumentBuilder builder = factory.newDocumentBuilder(); 189 FmEntityResolver er = new FmEntityResolver(); 190 builder.setEntityResolver(er); 191 doc = builder.parse(createInputSource(null, raw)); 192 if(er.getCallCount() == 0) { 195 cachedTree.set(doc, raw); 196 } 197 } 198 return doc; 199 } 200 catch (Exception e) 201 { 202 throw new FunctionCallException("Failed to parse document for URI: " + uri, e); 203 } 204 } 205 }; 206 207 static Template getTemplate(String systemId) throws IOException { 208 Environment env = Environment.getCurrentEnvironment(); 209 String encoding = env.getTemplate().getEncoding(); 210 if (encoding == null) { 211 encoding = env.getConfiguration().getEncoding(env.getLocale()); 212 } 213 String templatePath = env.getTemplate().getName(); 214 int lastSlash = templatePath.lastIndexOf('/'); 215 templatePath = lastSlash == -1 ? "" : templatePath.substring(0, lastSlash + 1); 216 systemId = TemplateCache.getFullTemplatePath(env, templatePath, systemId); 217 Template raw = env.getConfiguration().getTemplate(systemId, env.getLocale(), encoding, false); 218 return raw; 219 } 220 221 private static InputSource createInputSource(String publicId, Template raw) throws IOException , SAXException { 222 StringWriter sw = new StringWriter (); 223 try { 224 raw.process(Collections12.EMPTY_MAP, sw); 225 } 226 catch(TemplateException e) { 227 throw new SAXException (e); 228 } 229 InputSource is = new InputSource (); 230 is.setPublicId(publicId); 231 is.setSystemId(raw.getName()); 232 is.setCharacterStream(new StringReader (sw.toString())); 233 return is; 234 } 235 236 private static class FmEntityResolver implements EntityResolver { 237 private int callCount = 0; 238 239 public InputSource resolveEntity(String publicId, String systemId) 240 throws SAXException , IOException { 241 ++callCount; 242 return createInputSource(publicId, getTemplate(systemId)); 243 } 244 245 int getCallCount() { 246 return callCount; 247 } 248 }; 249 } 250 | Popular Tags |