http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl deleted file mode 100644 index edc3b4a..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl +++ /dev/null @@ -1,28 +0,0 @@ -<#ftl ns_prefixes = {"D": "http://y.com", "xx" : "http://x.com"}> -<#-- - 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. ---> -<#assign r = doc["N:root"]> -${r["N:t1"][0]?default('-')} = No NS -${r["xx:t2"][0]?default('-')} = x NS -${r["t3"][0]?default('-')} = y NS -${r["xx:t4"][0]?default('-')} = x NS -${r["//t1"][0]?default('-')} = No NS -${r["//t2"][0]?default('-')} = - -${r["//t3"][0]?default('-')} = - -${r["//t4"][0]?default('-')} = -
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml index 866da69..159266b 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml @@ -73,7 +73,6 @@ Note that for the incompatible_improvements setting you can specify a list of ve <testCase name="default-object-wrapper"> <setting api_builtin_enabled="true" /> </testCase> - <testCase name="default-xmlns" /> <testCase name="encoding-builtins" /> <testCase name="escapes" /> <testCase name="hashliteral" /> @@ -172,13 +171,6 @@ Note that for the incompatible_improvements setting you can specify a list of ve <testCase name="variables"/> <testCase name="whitespace-trim"/> <testCase name="wstrip-in-header"/> - <testCase name="xml-fragment" /> - <testCase name="xmlns1" /> - <testCase name="xmlns2" template="xmlns1.ftl" expected="xmlns1.txt" /> - <testCase name="xmlns3" /> - <testCase name="xmlns4" /> - <testCase name="xmlns5" /> - <testCase name="xml-ns_prefix-scope" template="xml-ns_prefix-scope-main.ftl" /> <testCase name="hashconcat"/> <testCase name="new-defaultresolver" /> <testCase name="new-unrestricted" template="new-defaultresolver.ftl" expected="new-defaultresolver.txt"> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml deleted file mode 100644 index d1fe3dc..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0"?> -<!-- - 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. ---> -<person> - <gender>male</gender> - <name>pradeep</name> - <dob>12th August</dob><address>Chennai, India</address> - <!--This is a comment Node --> - <?xml-stylesheet type="text/css" href="style.css"?> - <profession>Software Engineer</profession> - <![CDATA[ ]]> - <hobby>gardening</hobby> - <![CDATA[this is a valid cdata]]> - <phone>12345678</phone> -</person> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/build.gradle ---------------------------------------------------------------------- diff --git a/freemarker-core/build.gradle b/freemarker-core/build.gradle index 9de09c4..5adcf87 100644 --- a/freemarker-core/build.gradle +++ b/freemarker-core/build.gradle @@ -27,21 +27,9 @@ FreeMarker template engine, core module. This module covers all basic functional many applications.""" dependencies { - // Note that commond dependencies are added in the root project. - - // ------------------------------------------------------------------------ - // For the main artifact + // Note that common dependencies are added in the root project. compileOnly "org.zeroturnaround:javarebel-sdk:1.2.2" - - // TODO These will be moved to freemarker-dom module: - - compileOnly "jaxen:jaxen:1.0-FCS" - compileOnly "saxpath:saxpath:1.0-FCS" - compileOnly("xalan:xalan:2.7.0") { - // xml-apis is part of Java SE since version 1.4: - exclude group: "xml-apis", module: "xml-apis" - } } compileJavacc { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java index acf43df..0fa7e46 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java @@ -19,10 +19,8 @@ package org.apache.freemarker.core.model; -import org.apache.freemarker.dom.NodeModel; - /** - * A {@link NodeModel} that supports navigating to the previous and next sibling nodes. + * A {@link TemplateNodeModel} that supports navigating to the previous and next sibling nodes. * * @since 2.3.26 */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java index 7e04776..09e1e16 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java @@ -28,6 +28,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -65,8 +67,7 @@ import org.apache.freemarker.core.model.WrapperTemplateModel; import org.apache.freemarker.core.util.BugException; import org.apache.freemarker.core.util.CommonBuilder; import org.apache.freemarker.core.util._ClassUtil; -import org.apache.freemarker.dom.NodeModel; -import org.w3c.dom.Node; +import org.apache.freemarker.core.util._NullArgumentException; /** * The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to invoke instances of @@ -157,6 +158,11 @@ public class DefaultObjectWrapper implements RichObjectWrapper { @Deprecated // Only exists to keep some JUnit tests working... [FM3] private final boolean useModelCache; + /** Extensions applicable at the beginning of wrap(Object); null if it would be 0 length otherwise. */ + private final DefaultObjectWrapperExtension[] extensionsBeforeWrapSpecialObject; + /** Extensions applicable at the end of wrap(Object); null if it would be 0 length otherwise. */ + private final DefaultObjectWrapperExtension[] extensionsAfterWrapSpecialObject; + private final Version incompatibleImprovements; /** @@ -165,7 +171,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { * @param finalizeConstruction Decides if the construction is finalized now, or the caller will do some more * adjustments on the instance and then call {@link #finalizeConstruction()} itself. */ - protected DefaultObjectWrapper(ExtendableBuilder builder, boolean finalizeConstruction) { + protected DefaultObjectWrapper(ExtendableBuilder<?, ?> builder, boolean finalizeConstruction) { incompatibleImprovements = builder.getIncompatibleImprovements(); // normalized defaultDateType = builder.getDefaultDateType(); @@ -189,6 +195,33 @@ public class DefaultObjectWrapper implements RichObjectWrapper { enumModels = new EnumModels(this); useModelCache = builder.getUseModelCache(); + int extsAfterWSOCnt = 0; + int extsBeforeWSOCnt = 0; + for (DefaultObjectWrapperExtension ext : builder.getExtensions()) { + if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT) { + extsAfterWSOCnt++; + } else if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.BEFORE_WRAP_SPECIAL_OBJECT) { + extsBeforeWSOCnt++; + } else { + throw new BugException(); + } + } + extensionsAfterWrapSpecialObject = extsAfterWSOCnt != 0 + ? new DefaultObjectWrapperExtension[extsAfterWSOCnt] : null; + extensionsBeforeWrapSpecialObject = extsBeforeWSOCnt != 0 + ? new DefaultObjectWrapperExtension[extsBeforeWSOCnt] : null; + int extsAfterWSOIdx = 0; + int extsBeforeWSOIdx = 0; + for (DefaultObjectWrapperExtension ext : builder.getExtensions()) { + if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT) { + extensionsAfterWrapSpecialObject[extsAfterWSOIdx++] = ext; + } else if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.BEFORE_WRAP_SPECIAL_OBJECT) { + extensionsBeforeWrapSpecialObject[extsBeforeWSOIdx++] = ext; + } else { + throw new BugException(); + } + } + finalizeConstruction(); } @@ -330,11 +363,20 @@ public class DefaultObjectWrapper implements RichObjectWrapper { } /** - * Wraps the parameter object to {@link TemplateModel} interface(s). Simple types like numbers, strings, booleans - * and dates will be wrapped into the corresponding {@code SimpleXxx} classes (like {@link SimpleNumber}). - * {@link Map}-s, {@link List}-s, other {@link Collection}-s, arrays and {@link Iterator}-s will be wrapped into the - * corresponding {@code DefaultXxxAdapter} classes ({@link DefaultMapAdapter}), depending on). After that, the - * wrapping is handled by {@link #handleNonBasicTypes(Object)}, so see more there. + * Wraps the parameter object to {@link TemplateModel} interface(s). The wrapping logic uses several phases, + * where if a stage manages to wrap the object, this method immediately returns with the result. The stages are + * (executed in this order): + * <ol> + * <li>If the value is {@code null} or {@link TemplateModel} it's returned as is.</li> + * <li>If the value is a {@link TemplateModelAdapter}, {@link TemplateModelAdapter#getTemplateModel()} is + * returned.</li> + * <li>{@link ExtendableBuilder#extensions extensions} which subscribe to the + * {@link DefaultObjectWrapperExtensionPhase#BEFORE_WRAP_SPECIAL_OBJECT} phase try to wrap the object</li> + * <li>{@link #wrapSpecialObject(Object)} tries to wrap the object</li> + * <li>{@link ExtendableBuilder#extensions extensions} which subscribe to the + * {@link DefaultObjectWrapperExtensionPhase#AFTER_WRAP_SPECIAL_OBJECT} phase try to wrap the object</li> + * <li>{@link #wrapGenericObject(Object)} wraps the object (or if it can't, it must throw exception)</li> + * </ol> */ @Override public TemplateModel wrap(Object obj) throws TemplateModelException { @@ -348,6 +390,40 @@ public class DefaultObjectWrapper implements RichObjectWrapper { return ((TemplateModelAdapter) obj).getTemplateModel(); } + if (extensionsBeforeWrapSpecialObject != null) { + for (DefaultObjectWrapperExtension ext : extensionsBeforeWrapSpecialObject) { + TemplateModel tm = ext.wrap(obj); + if (tm != null) { + return tm; + } + } + } + + { + TemplateModel tm = wrapSpecialObject(obj); + if (tm != null) { + return tm; + } + } + + if (extensionsAfterWrapSpecialObject != null) { + for (DefaultObjectWrapperExtension ext : extensionsAfterWrapSpecialObject) { + TemplateModel tm = ext.wrap(obj); + if (tm != null) { + return tm; + } + } + } + + return wrapGenericObject(obj); + } + + /** + * Wraps non-generic objects; see {@link #wrap(Object)} for more. + * + * @return {@code null} if the object was not of a type that's wrapped "specially". + */ + protected TemplateModel wrapSpecialObject(Object obj) { if (obj instanceof String) { return new SimpleScalar((String) obj); } @@ -357,7 +433,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { if (obj instanceof Boolean) { return obj.equals(Boolean.TRUE) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; } - if (obj instanceof java.util.Date) { + if (obj instanceof Date) { if (obj instanceof java.sql.Date) { return new SimpleDate((java.sql.Date) obj); } @@ -367,7 +443,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { if (obj instanceof java.sql.Timestamp) { return new SimpleDate((java.sql.Timestamp) obj); } - return new SimpleDate((java.util.Date) obj, getDefaultDateType()); + return new SimpleDate((Date) obj, getDefaultDateType()); } final Class<?> objClass = obj.getClass(); if (objClass.isArray()) { @@ -390,30 +466,21 @@ public class DefaultObjectWrapper implements RichObjectWrapper { if (obj instanceof Enumeration) { return DefaultEnumerationAdapter.adapt((Enumeration<?>) obj, this); } - - return handleNonBasicTypes(obj); + if (obj instanceof ResourceBundle) { + return new ResourceBundleModel((ResourceBundle) obj, this); + } + return null; } /** - * Called for an object that isn't considered to be of a "basic" Java type, like for all application specific types, - * but currently also for {@link Node}-s and {@link ResourceBundle}-s. - * + * Called for an object that isn't treated specially by this {@link ObjectWrapper}; see {@link #wrap(Object)} for + * more. The implementation in {@link DefaultObjectWrapper} wraps the object into a {@link BeanAndStringModel}. * <p> - * When you override this method, you should first decide if you want to wrap the object in a custom way (and if so - * then do it and return with the result), and if not, then you should call the super method (assuming the default - * behavior is fine with you). + * Note that if you want to wrap some classes in a custom way, you shouldn't override this method. Instead, either + * override {@link #wrapSpecialObject(Object)}}, or don't subclass the {@link ObjectWrapper} at all, and + * just set the {@link ExtendableBuilder#getExtensions() extensions} configuration setting of it. */ - // [FM3] This is an awkward temporary solution, rework it. - protected TemplateModel handleNonBasicTypes(Object obj) throws TemplateModelException { - // [FM3] Via plugin mechanism, not by default anymore - if (obj instanceof Node) { - return NodeModel.wrap((Node) obj); - } - - if (obj instanceof ResourceBundle) { - return new ResourceBundleModel((ResourceBundle) obj, this); - } - + protected TemplateModel wrapGenericObject(Object obj) throws TemplateModelException { return new BeanAndStringModel(obj, this); } @@ -1339,10 +1406,12 @@ public class DefaultObjectWrapper implements RichObjectWrapper { private boolean useModelCacheSet; private boolean usePrivateCaches; private boolean usePrivateCachesSet; + private List<DefaultObjectWrapperExtension> extensions = Collections.emptyList(); + private boolean extensionsSet; // Attention! // - As this object is a cache key, non-normalized field values should be avoided. - // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens, - // there will be no unset fields. + // - Fields with default values must be set until the end of the constructor to ensure that when the lookup + // happens, there will be no unset fields. // - If you add a new field, review all methods in this class /** @@ -1415,6 +1484,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { result = prime * result + (builder.getUseModelCache() ? 1231 : 1237); result = prime * result + (builder.getUsePrivateCaches() ? 1231 : 1237); result = prime * result + builder.classIntrospectorBuilder.hashCode(); + result = prime * result + builder.getExtensions().hashCode(); return result; } @@ -1443,6 +1513,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { if (thisBuilder.isStrict() != thatBuilder.isStrict()) return false; if (thisBuilder.getUseModelCache() != thatBuilder.getUseModelCache()) return false; if (thisBuilder.getUsePrivateCaches() != thatBuilder.getUsePrivateCaches()) return false; + if (!thisBuilder.getExtensions().equals(thatBuilder.getExtensions())) return false; return thisBuilder.classIntrospectorBuilder.equals(thatBuilder.classIntrospectorBuilder); } @@ -1631,8 +1702,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper { } /** - * Tells if the instance cerates should try to caches with other {@link DefaultObjectWrapper} instances (where - * possible), or it should always invoke its own caches and not share that with anyone else. + * Tells if the instance creates should share caches with other {@link DefaultObjectWrapper} instances + * (where possible), or it should always invoke its own caches and not share that with anyone else. * */ public void setUsePrivateCaches(boolean usePrivateCaches) { this.usePrivateCaches = usePrivateCaches; @@ -1654,6 +1725,56 @@ public class DefaultObjectWrapper implements RichObjectWrapper { return self(); } + /** + * Extensions are used for dynamically decorating the {@link #wrap(Object)} method. In effects this is very + * similar to extending {@link DefaultObjectWrapper} and overriding its {@link #wrap(Object)}, and adding + * some wrapping logic before and/or after calling {@code super.wrap(obj)} (often referred to as decorating). + * But with this approach instead of subclassing {@link DefaultObjectWrapper} and its builder class, you + * simply list the desired extensions when you build the {@link DefaultObjectWrapper}. This is usually more + * convenient, and more flexible (what extensions you add can be decided on runtime factors) than the + * subclassing approach. + * + * @return An unmodifiable {@link List}. + */ + public List<? extends DefaultObjectWrapperExtension> getExtensions() { + return extensions; + } + + /** + * Setter pair of {@link #getExtensions()}. + * + * @param extensions The list of extensions; can't be {@code null}. + * The {@link List} list is copied, so further changes to the + * {@link List} passed in won't affect the value of this setting. + */ + public void setExtensions(List<? extends DefaultObjectWrapperExtension> extensions) { + _NullArgumentException.check("extensions", extensions); + this.extensions = Collections.unmodifiableList(new ArrayList(extensions)); + this.extensionsSet = true; + } + + /** + * Fluent API equivalent of {@link #setExtensions(List)}. + */ + public SelfT extensions(List<? extends DefaultObjectWrapperExtension> extensions) { + setExtensions(extensions); + return self(); + } + + /** + * Convenience varargs overload for calling {@link #extensions(List)}. + */ + public SelfT extensions(DefaultObjectWrapperExtension... extensions) { + return extensions(Arrays.asList(extensions)); + } + + /** + * Tells if the property was explicitly set, as opposed to just holding its default value. + */ + public boolean isExtensionsSet() { + return extensionsSet; + } + public int getExposureLevel() { return classIntrospectorBuilder.getExposureLevel(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java new file mode 100644 index 0000000..2d56486 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java @@ -0,0 +1,48 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; + +/** + * See the {@link DefaultObjectWrapper.ExtendableBuilder#getExtensions() extensions} setting of + * {@link DefaultObjectWrapper}. + */ +public abstract class DefaultObjectWrapperExtension { + + /** + * Specifies when {@link DefaultObjectWrapperExtension#wrap(Object)} is invoked inside + * {@link DefaultObjectWrapper#wrap(Object)}. + */ + public DefaultObjectWrapperExtensionPhase getPhase() { + return DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT; + } + + /** + * @param obj + * The object to wrap; never {@code null} or a {@link TemplateModel} or a {@link TemplateModelAdapter}. + * + * @return {@code null} if this {@link DefaultObjectWrapperExtension} doesn't handle this object the wrapped object + * otherwise. + */ + public abstract TemplateModel wrap(Object obj); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java new file mode 100644 index 0000000..4ef1aa4 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +/** + * Used for the return value of {@link DefaultObjectWrapperExtension#getPhase()}. + */ +public enum DefaultObjectWrapperExtensionPhase { + + /** + * Indicates that the {@link DefaultObjectWrapperExtension} should be invoked before + * {@link DefaultObjectWrapper#wrapSpecialObject(Object)}. + */ + BEFORE_WRAP_SPECIAL_OBJECT, + + /** + * Indicates that the {@link DefaultObjectWrapperExtension} should be invoked after + * {@link DefaultObjectWrapper#wrapSpecialObject(Object)}. + */ + AFTER_WRAP_SPECIAL_OBJECT + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java index e456dc6..43d47c0 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java @@ -45,7 +45,7 @@ public class RestrictedObjectWrapper extends DefaultObjectWrapper { * In this implementation, this just throws an exception. */ @Override - protected TemplateModel handleNonBasicTypes(Object obj) throws TemplateModelException { + protected TemplateModel wrapGenericObject(Object obj) throws TemplateModelException { throw new TemplateModelException("RestrictedObjectWrapper deliberately won't wrap this type: " + obj.getClass().getName()); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java deleted file mode 100644 index ca6ac6b..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -/** - * The special hash keys that start with "@@". - */ -enum AtAtKey { - - MARKUP("@@markup"), - NESTED_MARKUP("@@nested_markup"), - ATTRIBUTES_MARKUP("@@attributes_markup"), - TEXT("@@text"), - START_TAG("@@start_tag"), - END_TAG("@@end_tag"), - QNAME("@@qname"), - NAMESPACE("@@namespace"), - LOCAL_NAME("@@local_name"), - ATTRIBUTES("@@"), - PREVIOUS_SIBLING_ELEMENT("@@previous_sibling_element"), - NEXT_SIBLING_ELEMENT("@@next_sibling_element"); - - private final String key; - - public String getKey() { - return key; - } - - AtAtKey(String key) { - this.key = key; - } - - public static boolean containsKey(String key) { - for (AtAtKey item : AtAtKey.values()) { - if (item.getKey().equals(key)) { - return true; - } - } - return false; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java deleted file mode 100644 index cc510c4..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.w3c.dom.Attr; - -class AttributeNodeModel extends NodeModel implements TemplateScalarModel { - - public AttributeNodeModel(Attr att) { - super(att); - } - - @Override - public String getAsString() { - return ((Attr) node).getValue(); - } - - @Override - public String getNodeName() { - String result = node.getLocalName(); - if (result == null || result.equals("")) { - result = node.getNodeName(); - } - return result; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - String getQualifiedName() { - String nsURI = node.getNamespaceURI(); - if (nsURI == null || nsURI.equals("")) - return node.getNodeName(); - Environment env = Environment.getCurrentEnvironment(); - String defaultNS = env.getDefaultNS(); - String prefix = null; - if (nsURI.equals(defaultNS)) { - prefix = "D"; - } else { - prefix = env.getPrefixForNamespace(nsURI); - } - if (prefix == null) { - return null; - } - return prefix + ":" + node.getLocalName(); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java deleted file mode 100644 index 264c0db..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.w3c.dom.CharacterData; -import org.w3c.dom.Comment; - -class CharacterDataNodeModel extends NodeModel implements TemplateScalarModel { - - public CharacterDataNodeModel(CharacterData text) { - super(text); - } - - @Override - public String getAsString() { - return ((org.w3c.dom.CharacterData) node).getData(); - } - - @Override - public String getNodeName() { - return (node instanceof Comment) ? "@comment" : "@text"; - } - - @Override - public boolean isEmpty() { - return true; - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java deleted file mode 100644 index 876b3cf..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.model.TemplateHashModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; - -/** - * A class that wraps the root node of a parsed XML document, using - * the W3C DOM_WRAPPER API. - */ - -class DocumentModel extends NodeModel implements TemplateHashModel { - - private ElementModel rootElement; - - DocumentModel(Document doc) { - super(doc); - } - - @Override - public String getNodeName() { - return "@document"; - } - - @Override - public TemplateModel get(String key) throws TemplateModelException { - if (key.equals("*")) { - return getRootElement(); - } else if (key.equals("**")) { - NodeList nl = ((Document) node).getElementsByTagName("*"); - return new NodeListModel(nl, this); - } else if (DomStringUtil.isXMLNameLike(key)) { - ElementModel em = (ElementModel) NodeModel.wrap(((Document) node).getDocumentElement()); - if (em.matchesName(key, Environment.getCurrentEnvironment())) { - return em; - } else { - return new NodeListModel(this); - } - } - return super.get(key); - } - - ElementModel getRootElement() { - if (rootElement == null) { - rootElement = (ElementModel) wrap(((Document) node).getDocumentElement()); - } - return rootElement; - } - - @Override - public boolean isEmpty() { - return false; - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java deleted file mode 100644 index 3448f77..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.w3c.dom.DocumentType; -import org.w3c.dom.ProcessingInstruction; - -class DocumentTypeModel extends NodeModel { - - public DocumentTypeModel(DocumentType docType) { - super(docType); - } - - public String getAsString() { - return ((ProcessingInstruction) node).getData(); - } - - public TemplateSequenceModel getChildren() throws TemplateModelException { - throw new TemplateModelException("entering the child nodes of a DTD node is not currently supported"); - } - - @Override - public TemplateModel get(String key) throws TemplateModelException { - throw new TemplateModelException("accessing properties of a DTD is not currently supported"); - } - - @Override - public String getNodeName() { - return "@document_type$" + node.getNodeName(); - } - - @Override - public boolean isEmpty() { - return true; - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java deleted file mode 100644 index a1f6f0c..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -final class DomLog { - - private DomLog() { - // - } - - public static final Logger LOG = LoggerFactory.getLogger("org.apache.freemarker.dom"); - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java deleted file mode 100644 index f5b58f8..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -/** - * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! - * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can - * access things inside this package that users shouldn't. - */ -final class DomStringUtil { - - private DomStringUtil() { - // Not meant to be instantiated - } - - static boolean isXMLNameLike(String name) { - return isXMLNameLike(name, 0); - } - - /** - * Check if the name looks like an XML element name. - * - * @param firstCharIdx The index of the character in the string parameter that we treat as the beginning of the - * string to check. This is to spare substringing that has become more expensive in Java 7. - * - * @return whether the name is a valid XML element name. (This routine might only be 99% accurate. REVISIT) - */ - static boolean isXMLNameLike(String name, int firstCharIdx) { - int ln = name.length(); - for (int i = firstCharIdx; i < ln; i++) { - char c = name.charAt(i); - if (i == firstCharIdx && (c == '-' || c == '.' || Character.isDigit(c))) { - return false; - } - if (!Character.isLetterOrDigit(c) && c != '_' && c != '-' && c != '.') { - if (c == ':') { - if (i + 1 < ln && name.charAt(i + 1) == ':') { - // "::" is used in XPath - return false; - } - // We don't return here, as a lonely ":" is allowed. - } else { - return false; - } - } - } - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java deleted file mode 100644 index 220f414..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import java.util.Collections; - -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.Template; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.model.impl.SimpleScalar; -import org.apache.freemarker.core.util._StringUtil; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -class ElementModel extends NodeModel implements TemplateScalarModel { - - public ElementModel(Element element) { - super(element); - } - - @Override - public boolean isEmpty() { - return false; - } - - /** - * An Element node supports various hash keys. - * Any key that corresponds to the tag name of any child elements - * returns a sequence of those elements. The special key "*" returns - * all the element's direct children. - * The "**" key return all the element's descendants in the order they - * occur in the document. - * Any key starting with '@' is taken to be the name of an element attribute. - * The special key "@@" returns a hash of all the element's attributes. - * The special key "/" returns the root document node associated with this element. - */ - @Override - public TemplateModel get(String key) throws TemplateModelException { - if (key.equals("*")) { - NodeListModel ns = new NodeListModel(this); - TemplateSequenceModel children = getChildNodes(); - for (int i = 0; i < children.size(); i++) { - NodeModel child = (NodeModel) children.get(i); - if (child.node.getNodeType() == Node.ELEMENT_NODE) { - ns.add(child); - } - } - return ns; - } else if (key.equals("**")) { - return new NodeListModel(((Element) node).getElementsByTagName("*"), this); - } else if (key.startsWith("@")) { - if (key.startsWith("@@")) { - if (key.equals(AtAtKey.ATTRIBUTES.getKey())) { - return new NodeListModel(node.getAttributes(), this); - } else if (key.equals(AtAtKey.START_TAG.getKey())) { - NodeOutputter nodeOutputter = new NodeOutputter(node); - return new SimpleScalar(nodeOutputter.getOpeningTag((Element) node)); - } else if (key.equals(AtAtKey.END_TAG.getKey())) { - NodeOutputter nodeOutputter = new NodeOutputter(node); - return new SimpleScalar(nodeOutputter.getClosingTag((Element) node)); - } else if (key.equals(AtAtKey.ATTRIBUTES_MARKUP.getKey())) { - StringBuilder buf = new StringBuilder(); - NodeOutputter nu = new NodeOutputter(node); - nu.outputContent(node.getAttributes(), buf); - return new SimpleScalar(buf.toString().trim()); - } else if (key.equals(AtAtKey.PREVIOUS_SIBLING_ELEMENT.getKey())) { - Node previousSibling = node.getPreviousSibling(); - while (previousSibling != null && !isSignificantNode(previousSibling)) { - previousSibling = previousSibling.getPreviousSibling(); - } - return previousSibling != null && previousSibling.getNodeType() == Node.ELEMENT_NODE - ? wrap(previousSibling) : new NodeListModel(Collections.emptyList(), null); - } else if (key.equals(AtAtKey.NEXT_SIBLING_ELEMENT.getKey())) { - Node nextSibling = node.getNextSibling(); - while (nextSibling != null && !isSignificantNode(nextSibling)) { - nextSibling = nextSibling.getNextSibling(); - } - return nextSibling != null && nextSibling.getNodeType() == Node.ELEMENT_NODE - ? wrap(nextSibling) : new NodeListModel(Collections.emptyList(), null); - } else { - // We don't know anything like this that's element-specific; fall back - return super.get(key); - } - } else { // Starts with "@", but not with "@@" - if (DomStringUtil.isXMLNameLike(key, 1)) { - Attr att = getAttribute(key.substring(1)); - if (att == null) { - return new NodeListModel(this); - } - return wrap(att); - } else if (key.equals("@*")) { - return new NodeListModel(node.getAttributes(), this); - } else { - // We don't know anything like this that's element-specific; fall back - return super.get(key); - } - } - } else if (DomStringUtil.isXMLNameLike(key)) { - // We interpret key as an element name - NodeListModel result = ((NodeListModel) getChildNodes()).filterByName(key); - return result.size() != 1 ? result : result.get(0); - } else { - // We don't anything like this that's element-specific; fall back - return super.get(key); - } - } - - @Override - public String getAsString() throws TemplateModelException { - NodeList nl = node.getChildNodes(); - String result = ""; - for (int i = 0; i < nl.getLength(); i++) { - Node child = nl.item(i); - int nodeType = child.getNodeType(); - if (nodeType == Node.ELEMENT_NODE) { - String msg = "Only elements with no child elements can be processed as text." - + "\nThis element with name \"" - + node.getNodeName() - + "\" has a child element named: " + child.getNodeName(); - throw new TemplateModelException(msg); - } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) { - result += child.getNodeValue(); - } - } - return result; - } - - @Override - public String getNodeName() { - String result = node.getLocalName(); - if (result == null || result.equals("")) { - result = node.getNodeName(); - } - return result; - } - - @Override - String getQualifiedName() { - String nodeName = getNodeName(); - String nsURI = getNodeNamespace(); - if (nsURI == null || nsURI.length() == 0) { - return nodeName; - } - Environment env = Environment.getCurrentEnvironment(); - String defaultNS = env.getDefaultNS(); - String prefix; - if (defaultNS != null && defaultNS.equals(nsURI)) { - prefix = ""; - } else { - prefix = env.getPrefixForNamespace(nsURI); - - } - if (prefix == null) { - return null; // We have no qualified name, because there is no prefix mapping - } - if (prefix.length() > 0) { - prefix += ":"; - } - return prefix + nodeName; - } - - private Attr getAttribute(String qname) { - Element element = (Element) node; - Attr result = element.getAttributeNode(qname); - if (result != null) - return result; - int colonIndex = qname.indexOf(':'); - if (colonIndex > 0) { - String prefix = qname.substring(0, colonIndex); - String uri; - if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) { - uri = Environment.getCurrentEnvironment().getDefaultNS(); - } else { - uri = Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix); - } - String localName = qname.substring(1 + colonIndex); - if (uri != null) { - result = element.getAttributeNodeNS(uri, localName); - } - } - return result; - } - - private boolean isSignificantNode(Node node) throws TemplateModelException { - return (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) - ? !isBlankXMLText(node.getTextContent()) - : node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE && node.getNodeType() != Node.COMMENT_NODE; - } - - private boolean isBlankXMLText(String s) { - if (s == null) { - return true; - } - for (int i = 0; i < s.length(); i++) { - if (!isXMLWhiteSpace(s.charAt(i))) { - return false; - } - } - return true; - } - - /** - * White space according the XML spec. - */ - private boolean isXMLWhiteSpace(char c) { - return c == ' ' || c == '\t' || c == '\n' | c == '\r'; - } - - boolean matchesName(String name, Environment env) { - return _StringUtil.matchesQName(name, getNodeName(), getNodeNamespace(), env); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java deleted file mode 100644 index 3e52836..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.apache.freemarker.core.CustomStateKey; -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.Template; -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateDateModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateNumberModel; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.util.UndeclaredThrowableException; -import org.apache.freemarker.core.util._ObjectHolder; -import org.jaxen.BaseXPath; -import org.jaxen.Function; -import org.jaxen.FunctionCallException; -import org.jaxen.FunctionContext; -import org.jaxen.JaxenException; -import org.jaxen.NamespaceContext; -import org.jaxen.Navigator; -import org.jaxen.UnresolvableException; -import org.jaxen.VariableContext; -import org.jaxen.XPathFunctionContext; -import org.jaxen.dom.DocumentNavigator; -import org.w3c.dom.Document; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - - -/** - */ -class JaxenXPathSupport implements XPathSupport { - - private static final CustomStateKey<Map<String, BaseXPath>> XPATH_CACHE_ATTR - = new CustomStateKey<Map<String, BaseXPath>>() { - @Override - protected Map<String, BaseXPath> create() { - return new HashMap<String, BaseXPath>(); - } - }; - - // [2.4] Can't we just use Collections.emptyList()? - private final static ArrayList EMPTY_ARRAYLIST = new ArrayList(); - - @Override - public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException { - try { - BaseXPath xpath; - Map<String, BaseXPath> xpathCache = Environment.getCurrentEnvironmentNotNull().getCurrentTemplateNotNull() - .getCustomState(XPATH_CACHE_ATTR); - synchronized (xpathCache) { - xpath = xpathCache.get(xpathQuery); - if (xpath == null) { - xpath = new BaseXPath(xpathQuery, FM_DOM_NAVIGATOR); - xpath.setNamespaceContext(customNamespaceContext); - xpath.setFunctionContext(FM_FUNCTION_CONTEXT); - xpath.setVariableContext(FM_VARIABLE_CONTEXT); - xpathCache.put(xpathQuery, xpath); - } - } - List result = xpath.selectNodes(context != null ? context : EMPTY_ARRAYLIST); - if (result.size() == 1) { - return NodeQueryResultItemObjectWrapper.INSTANCE.wrap(result.get(0)); - } - NodeListModel nlm = new NodeListModel(result, null); - nlm.xpathSupport = this; - return nlm; - } catch (UndeclaredThrowableException e) { - Throwable t = e.getUndeclaredThrowable(); - if (t instanceof TemplateModelException) { - throw (TemplateModelException) t; - } - throw e; - } catch (JaxenException je) { - throw new TemplateModelException(je); - } - } - - static private final NamespaceContext customNamespaceContext = new NamespaceContext() { - - @Override - public String translateNamespacePrefixToUri(String prefix) { - if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) { - return Environment.getCurrentEnvironment().getDefaultNS(); - } - return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix); - } - }; - - private static final VariableContext FM_VARIABLE_CONTEXT = new VariableContext() { - @Override - public Object getVariableValue(String namespaceURI, String prefix, String localName) - throws UnresolvableException { - try { - TemplateModel model = Environment.getCurrentEnvironment().getVariable(localName); - if (model == null) { - throw new UnresolvableException("Variable \"" + localName + "\" not found."); - } - if (model instanceof TemplateScalarModel) { - return ((TemplateScalarModel) model).getAsString(); - } - if (model instanceof TemplateNumberModel) { - return ((TemplateNumberModel) model).getAsNumber(); - } - if (model instanceof TemplateDateModel) { - return ((TemplateDateModel) model).getAsDate(); - } - if (model instanceof TemplateBooleanModel) { - return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); - } - } catch (TemplateModelException e) { - throw new UndeclaredThrowableException(e); - } - throw new UnresolvableException( - "Variable \"" + localName + "\" exists, but it's not a string, number, date, or boolean"); - } - }; - - private static final FunctionContext FM_FUNCTION_CONTEXT = new XPathFunctionContext() { - @Override - public Function getFunction(String namespaceURI, String prefix, String localName) - throws UnresolvableException { - try { - return super.getFunction(namespaceURI, prefix, localName); - } catch (UnresolvableException e) { - return super.getFunction(null, null, localName); - } - } - }; - - /** - * Stores the the template parsed as {@link Document} in the template itself. - */ - private static final CustomStateKey<_ObjectHolder<Document>> FM_DOM_NAVIAGOTOR_CACHED_DOM - = new CustomStateKey<_ObjectHolder<Document>>() { - @Override - protected _ObjectHolder<Document> create() { - return new _ObjectHolder<>(null); - } - }; - - private static final Navigator FM_DOM_NAVIGATOR = new DocumentNavigator() { - @Override - public Object getDocument(String uri) throws FunctionCallException { - try { - Template raw = getTemplate(uri); - _ObjectHolder<Document> docHolder = Environment.getCurrentEnvironmentNotNull() - .getCurrentTemplateNotNull().getCustomState(FM_DOM_NAVIAGOTOR_CACHED_DOM); - synchronized (docHolder) { - Document doc = docHolder.get(); - if (doc == null) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - FmEntityResolver er = new FmEntityResolver(); - builder.setEntityResolver(er); - doc = builder.parse(createInputSource(null, raw)); - // If the entity resolver got called 0 times, the document - // is standalone, so we can safely cache it - if (er.getCallCount() == 0) { - docHolder.set(doc); - } - } - return doc; - } - } catch (Exception e) { - throw new FunctionCallException("Failed to parse document for URI: " + uri, e); - } - } - }; - - // [FM3] Look into this "hidden" feature - static Template getTemplate(String systemId) throws IOException { - Environment env = Environment.getCurrentEnvironment(); - String templatePath = env.getCurrentTemplate().getLookupName(); - int lastSlash = templatePath.lastIndexOf('/'); - templatePath = lastSlash == -1 ? "" : templatePath.substring(0, lastSlash + 1); - systemId = env.toFullTemplateName(templatePath, systemId); - return env.getConfiguration().getTemplate(systemId, env.getLocale()); - } - - private static InputSource createInputSource(String publicId, Template raw) throws IOException, SAXException { - StringWriter sw = new StringWriter(); - try { - raw.process(Collections.EMPTY_MAP, sw); - } catch (TemplateException e) { - throw new SAXException(e); - } - InputSource is = new InputSource(); - is.setPublicId(publicId); - is.setSystemId(raw.getLookupName()); - is.setCharacterStream(new StringReader(sw.toString())); - return is; - } - - private static class FmEntityResolver implements EntityResolver { - private int callCount = 0; - - @Override - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException { - ++callCount; - return createInputSource(publicId, getTemplate(systemId)); - } - - int getCallCount() { - return callCount; - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java deleted file mode 100644 index 333bb5c..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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 org.apache.freemarker.dom; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel; -import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateDateModel; -import org.apache.freemarker.core.model.TemplateHashModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateNodeModel; -import org.apache.freemarker.core.model.TemplateNumberModel; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.model.impl.SimpleScalar; -import org.apache.freemarker.core.model.impl.SimpleSequence; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * Used when the result set contains 0 or multiple nodes; shouldn't be used when you have exactly 1 node. For exactly 1 - * node, use {@link NodeModel#wrap(Node)}, because {@link NodeModel} subclasses can have extra features building on that - * restriction, like single elements with text content can be used as FTL string-s. - * <p> - * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as - * {@linkplain Configuration#getSharedVariables() shared variable}. - */ -class NodeListModel extends SimpleSequence implements TemplateHashModel, _UnexpectedTypeErrorExplainerTemplateModel { - - // [2.4] make these private - NodeModel contextNode; - XPathSupport xpathSupport; - - NodeListModel(Node contextNode) { - this(NodeModel.wrap(contextNode)); - } - - NodeListModel(NodeModel contextNode) { - super(NodeQueryResultItemObjectWrapper.INSTANCE); - this.contextNode = contextNode; - } - - NodeListModel(NodeList nodeList, NodeModel contextNode) { - super(NodeQueryResultItemObjectWrapper.INSTANCE); - for (int i = 0; i < nodeList.getLength(); i++) { - list.add(nodeList.item(i)); - } - this.contextNode = contextNode; - } - - NodeListModel(NamedNodeMap nodeList, NodeModel contextNode) { - super(NodeQueryResultItemObjectWrapper.INSTANCE); - for (int i = 0; i < nodeList.getLength(); i++) { - list.add(nodeList.item(i)); - } - this.contextNode = contextNode; - } - - NodeListModel(List list, NodeModel contextNode) { - super(list, NodeQueryResultItemObjectWrapper.INSTANCE); - this.contextNode = contextNode; - } - - NodeListModel filterByName(String name) throws TemplateModelException { - NodeListModel result = new NodeListModel(contextNode); - int size = size(); - if (size == 0) { - return result; - } - Environment env = Environment.getCurrentEnvironment(); - for (int i = 0; i < size; i++) { - NodeModel nm = (NodeModel) get(i); - if (nm instanceof ElementModel) { - if (((ElementModel) nm).matchesName(name, env)) { - result.add(nm); - } - } - } - return result; - } - - @Override - public boolean isEmpty() { - return size() == 0; - } - - @Override - public TemplateModel get(String key) throws TemplateModelException { - if (size() == 1) { - NodeModel nm = (NodeModel) get(0); - return nm.get(key); - } - if (key.startsWith("@@")) { - if (key.equals(AtAtKey.MARKUP.getKey()) - || key.equals(AtAtKey.NESTED_MARKUP.getKey()) - || key.equals(AtAtKey.TEXT.getKey())) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < size(); i++) { - NodeModel nm = (NodeModel) get(i); - TemplateScalarModel textModel = (TemplateScalarModel) nm.get(key); - result.append(textModel.getAsString()); - } - return new SimpleScalar(result.toString()); - } else if (key.length() != 2 /* to allow "@@" to fall through */) { - // As @@... would cause exception in the XPath engine, we throw a nicer exception now. - if (AtAtKey.containsKey(key)) { - throw new TemplateModelException( - "\"" + key + "\" is only applicable to a single XML node, but it was applied on " - + (size() != 0 - ? size() + " XML nodes (multiple matches)." - : "an empty list of XML nodes (no matches).")); - } else { - throw new TemplateModelException("Unsupported @@ key: " + key); - } - } - } - if (DomStringUtil.isXMLNameLike(key) - || ((key.startsWith("@") - && (DomStringUtil.isXMLNameLike(key, 1) || key.equals("@@") || key.equals("@*")))) - || key.equals("*") || key.equals("**")) { - NodeListModel result = new NodeListModel(contextNode); - for (int i = 0; i < size(); i++) { - NodeModel nm = (NodeModel) get(i); - if (nm instanceof ElementModel) { - TemplateSequenceModel tsm = (TemplateSequenceModel) nm.get(key); - if (tsm != null) { - int size = tsm.size(); - for (int j = 0; j < size; j++) { - result.add(tsm.get(j)); - } - } - } - } - if (result.size() == 1) { - return result.get(0); - } - return result; - } - XPathSupport xps = getXPathSupport(); - if (xps != null) { - Object context = (size() == 0) ? null : rawNodeList(); - return xps.executeQuery(context, key); - } else { - throw new TemplateModelException( - "Can't try to resolve the XML query key, because no XPath support is available. " - + "This is either malformed or an XPath expression: " + key); - } - } - - private List rawNodeList() throws TemplateModelException { - int size = size(); - ArrayList al = new ArrayList(size); - for (int i = 0; i < size; i++) { - al.add(((NodeModel) get(i)).node); - } - return al; - } - - XPathSupport getXPathSupport() throws TemplateModelException { - if (xpathSupport == null) { - if (contextNode != null) { - xpathSupport = contextNode.getXPathSupport(); - } else if (size() > 0) { - xpathSupport = ((NodeModel) get(0)).getXPathSupport(); - } - } - return xpathSupport; - } - - @Override - public Object[] explainTypeError(Class[] expectedClasses) { - for (Class expectedClass : expectedClasses) { - if (TemplateScalarModel.class.isAssignableFrom(expectedClass) - || TemplateDateModel.class.isAssignableFrom(expectedClass) - || TemplateNumberModel.class.isAssignableFrom(expectedClass) - || TemplateBooleanModel.class.isAssignableFrom(expectedClass)) { - return newTypeErrorExplanation("string"); - } else if (TemplateNodeModel.class.isAssignableFrom(expectedClass)) { - return newTypeErrorExplanation("node"); - } - } - return null; - } - - private Object[] newTypeErrorExplanation(String type) { - return new Object[] { - "This XML query result can't be used as ", type, " because for that it had to contain exactly " - + "1 XML node, but it contains ", Integer.valueOf(size()), " nodes. " - + "That is, the constructing XML query has found ", - isEmpty() - ? "no matches." - : "multiple matches." - }; - } - -} \ No newline at end of file
