Merge pull request #83 from nolaviz/nolaviz-devel
Add fine-grained mixed-content support to markup output formats.
diff --git a/.gitignore b/.gitignore
index 5deb624..6a1796c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,9 @@
# 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
@@ -30,6 +30,7 @@
.classpath
.project
.settings
+bnd.bnd
.idea/
*.iml
diff --git a/build.xml b/build.xml
index f70b987..c219f48 100644
--- a/build.xml
+++ b/build.xml
@@ -7,9 +7,9 @@
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
@@ -41,7 +41,7 @@
<!-- Will be overidden on the Continous Integration server: -->
<property name="server.ivy.repo.root" value="${basedir}/build/dummy-server-ivy-repo" />
-
+
<property file="build.properties"/>
<condition property="has.explicit.boot.classpath.j2se1.7">
<isset property="boot.classpath.j2se1.7"/>
@@ -60,17 +60,17 @@
<!-- Note: Target "dist" doesn't allow using these. -->
<property name="boot.classpath.j2se1.7" value="${sun.boot.class.path}" />
<property name="boot.classpath.j2se1.8" value="${sun.boot.class.path}" />
-
+
<!-- For checking the correctness of the boot.classpath.j2se* -->
<available classpath="${boot.classpath.j2se1.7}"
- classname="java.nio.file.Path" ignoresystemclasses="true"
+ classname="java.nio.file.Path" ignoresystemclasses="true"
property="boot.classpath.j2se1.7.correct"
/>
<available classpath="${boot.classpath.j2se1.8}"
- classname="java.time.Instant" ignoresystemclasses="true"
+ classname="java.time.Instant" ignoresystemclasses="true"
property="boot.classpath.j2se1.8.correct"
/>
-
+
<!-- Set up version/timestamp filters and the version property: -->
<tstamp>
<format property="timestampNice" pattern="yyyy-MM-dd'T'HH:mm:ss'Z'"
@@ -91,17 +91,17 @@
<property file="build/version.properties.tmp" />
<delete file="build/version.properties.tmp" />
<filter token="version" value="${version}" />
-
+
<property name="dist.dir" value="build/dist" />
<property name="dist.archiveBaseName" value="apache-${mavenArtifactId}-${mavenVersion}" />
<property name="dist.bin.dir" value="${dist.dir}/bin/${dist.archiveBaseName}-bin" />
<property name="dist.src.dir" value="${dist.dir}/src/${dist.archiveBaseName}-src" />
-
+
<!-- ================================================================== -->
<!-- Initialization -->
<!-- ================================================================== -->
-
+
<target name="clean" description="get rid of all generated files">
<delete dir="build" />
<delete dir="META-INF" />
@@ -126,11 +126,11 @@
<get src="https://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
dest="${ivy.jar.file}" usetimestamp="true"/>
</target>
-
+
<!-- ================================================================= -->
<!-- Compilation -->
<!-- ================================================================= -->
-
+
<target name="javacc" depends="init" unless="parser.uptodate"
description="Build the parser from its grammar file"
>
@@ -139,7 +139,7 @@
uri="http://javacc.dev.java.net/"
classpathref="ivy.dep"
/>
-
+
<property name="_javaccOutputDir"
value="build/generated-sources/java/freemarker/core/"
/>
@@ -152,7 +152,7 @@
javacchome="build/javacc-home.tmp"
/>
<delete dir="build/javacc-home.tmp" />
-
+
<replace
file="${_javaccOutputDir}/FMParser.java"
token="private final LookaheadSuccess"
@@ -183,18 +183,18 @@
token="enum"
value="ENUM"
/>
-
+
<!-- As we have a modified version in src/main/java: -->
- <move
+ <move
file="${_javaccOutputDir}/ParseException.java"
tofile="${_javaccOutputDir}/ParseException.java.ignore"
/>
- <move
+ <move
file="${_javaccOutputDir}/TokenMgrError.java"
tofile="${_javaccOutputDir}/TokenMgrError.java.ignore"
/>
</target>
-
+
<target name="compile" depends="javacc">
<fail unless="boot.classpath.j2se1.7.correct"><!--
-->The "boot.classpath.j2se1.7" property value (${boot.classpath.j2se1.7}) <!--
@@ -214,7 +214,7 @@
-->Java 8: ${boot.classpath.j2se1.8}<!--
--></echo>
- <!-- Comment out @SuppressFBWarnings, as it causes compilation warnings in dependent Gradle projects -->
+ <!-- Comment out @SuppressFBWarnings, as it causes compilation warnings in dependent Gradle projects -->
<delete dir="build/src-main-java-filtered" />
<mkdir dir="build/src-main-java-filtered" />
<copy toDir="build/src-main-java-filtered">
@@ -232,12 +232,12 @@
>
<fileset dir="build/src-main-java-filtered" includes="**/*.java" />
</replaceregexp>
-
+
<mkdir dir="build/classes" />
<!-- Note: the "build.base" conf doesn't include optional FreeMarker dependencies. -->
<ivy:cachepath conf="build.base" pathid="ivy.dep" />
- <javac destdir="build/classes" deprecation="off"
+ <javac destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep"
@@ -247,7 +247,7 @@
freemarker/ext/jsp/**,
freemarker/ext/servlet/**,
freemarker/cache/WebappTemplateLoader.java,
-
+
freemarker/ext/jython/**,
freemarker/template/utility/JythonRuntime.java,
freemarker/ext/ant/**"
@@ -259,14 +259,14 @@
</javac>
<ivy:cachepath conf="build.base" pathid="ivy.dep" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep"
bootclasspath="${boot.classpath.j2se1.8}"
includes="freemarker/core/_Java8Impl.java"
/>
-
+
<rmic
base="build/classes" includes="freemarker/debug/impl/Rmi*Impl.class"
classpathref="ivy.dep"
@@ -274,7 +274,7 @@
/>
<ivy:cachepath conf="build.jsp2.0" pathid="ivy.dep.jsp2.0" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep.jsp2.0"
@@ -288,11 +288,11 @@
freemarker/ext/jsp/FreeMarkerJspFactory21.java,
freemarker/ext/jsp/FreeMarkerJspApplicationContext.java"
/>
-
+
<!-- There's no build.jsp2.0, as those classes are part of the common build subset. -->
-
+
<ivy:cachepath conf="build.jsp2.1" pathid="ivy.dep.jsp2.1" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep.jsp2.1"
@@ -304,7 +304,7 @@
/>
<ivy:cachepath conf="build.jython2.0" pathid="ivy.dep.jython2.0" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep.jython2.0"
@@ -317,9 +317,9 @@
freemarker/ext/jython/_Jython22VersionAdapter.java,
freemarker/ext/jython/_Jython25VersionAdapter.java"
/>
-
+
<ivy:cachepath conf="build.jython2.2" pathid="ivy.dep.jython2.2" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep.jython2.2"
@@ -327,9 +327,9 @@
includes="
freemarker/ext/jython/_Jython22VersionAdapter.java"
/>
-
+
<ivy:cachepath conf="build.jython2.5" pathid="ivy.dep.jython2.5" />
- <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
+ <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
includeantruntime="false"
classpathref="ivy.dep.jython2.5"
@@ -337,12 +337,12 @@
includes="
freemarker/ext/jython/_Jython25VersionAdapter.java"
/>
-
+
<rmic base="build/classes" classpathref="ivy.dep"
includes="build/src-main-java-filtered/freemarker/debug/impl/Rmi*Impl.class"
verify="yes" stubversion="1.2"
/>
-
+
<copy toDir="build/classes">
<fileset dir="src/main/resources"
excludes="
@@ -363,9 +363,9 @@
<target name="compileTest" depends="compile">
<mkdir dir="build/test-classes" />
-
+
<ivy:cachepath conf="build.test" pathid="ivy.dep.build.test" />
- <javac srcdir="src/test/java" destdir="build/test-classes" deprecation="off"
+ <javac srcdir="src/test/java" destdir="build/test-classes" deprecation="off"
debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
includeantruntime="false"
classpath="build/classes"
@@ -378,14 +378,14 @@
/>
</copy>
</target>
-
+
<target name="jar" depends="compile">
<ivy:cachepath pathid="ivy.dep" conf="bnd" />
<taskdef resource="aQute/bnd/ant/taskdef.properties"
uri="http://www.aqute.biz/bnd"
classpathref="ivy.dep"
/>
-
+
<!-- Hack: This file should be excluded, but I can't explain that to bnd. -->
<!-- We don't have this file in 2.4.X... yet?
<move
@@ -432,7 +432,7 @@
</batchtest>
</junit>
</target>
-
+
<!-- ================================================================= -->
<!-- Generate docs -->
<!-- ================================================================= -->
@@ -467,7 +467,7 @@
<exclude name="**/log/CommonsLoggingLoggerFactory.java" />
</fileset>
</copy>
-
+
<!-- conf="IDE": as that has to contain all depedencies -->
<ivy:cachepath conf="IDE" pathid="ivy.dep" />
<javadoc
@@ -496,14 +496,14 @@
</target>
<target name="javadoc" depends="_rawJavadoc, _fixJDK8JavadocCSS" description="Build the JavaDocs" />
-
+
<target name="_fixJDK8JavadocCSS" depends="_rawJavadoc" if="atLeastJDK8">
<property name="file" value="build/api/stylesheet.css" />
-
+
<available file="${file}" property="stylesheet.available"/>
<fail unless="stylesheet.available">CSS file not found: ${file}</fail>
<echo>Fixing JDK 8 CSS in ${file}</echo>
-
+
<!-- Tell that it's modified: -->
<replaceregexp
file="${file}" flags="gs" encoding="utf-8"
@@ -515,7 +515,7 @@
file="${file}" flags="gs" encoding="utf-8"
match="@import url\('resources/fonts/dejavu.css'\);\s*" replace=""
/>
-
+
<!-- Font family fixes: -->
<replaceregexp
file="${file}" flags="gsi" encoding="utf-8"
@@ -549,7 +549,7 @@
file="${file}" flags="gsi" encoding="utf-8"
match="(?<=[\s,:])Arial\s*,\s*Arial\b" replace="Arial"
/>
-
+
<!-- "Parameters:", "Returns:", "Throws:", "Since:", "See also:" etc. fixes: -->
<property name="ddSelectorStart" value="(?:\.contentContainer\s+\.(?:details|description)|\.serializedFormContainer)\s+dl\s+dd\b.*?\{[^\}]*\b" />
<property name="ddPropertyEnd" value="\b.+?;" />
@@ -564,11 +564,11 @@
match="(${ddSelectorStart})font-family${ddPropertyEnd}" replace="\1"
/>
</target>
-
+
<!-- ====================== -->
<!-- Manual -->
<!-- ====================== -->
-
+
<macrodef name="manual" uri="http://freemarker.org/util">
<attribute name="offline" />
<attribute name="locale" />
@@ -578,14 +578,14 @@
uri="http://freemarker.org/docgen"
classpathref="ivy.dep"
/>
-
+
<docgen:transform
srcdir="src/manual/@{locale}" destdir="build/manual/@{locale}"
offline="@{offline}"
/>
</sequential>
</macrodef>
-
+
<target name="manualOffline" depends="init" description="Build the Manual for offline use" >
<u:manual offline="true" locale="en_US" />
</target>
@@ -593,7 +593,7 @@
<target name="manualOnline" depends="init" description="Build the Manual to be upload to the FreeMarker homepage" >
<u:manual offline="false" locale="en_US" />
</target>
-
+
<target name="manualOffline_zh_CN" depends="init" description="Build the Manual for offline use" >
<u:manual offline="true" locale="zh_CN" />
</target>
@@ -601,7 +601,7 @@
<target name="manualOnline_zh_CN" depends="init" description="Build the Manual to be upload to the FreeMarker homepage" >
<u:manual offline="false" locale="zh_CN" />
</target>
-
+
<!-- ===================== -->
<!-- Distribution building -->
@@ -618,7 +618,7 @@
<antcall target="clean" /> <!-- To improve the reliability -->
<antcall target="_dist" />
</target>
-
+
<target name="_dist"
depends="test, jar, javadoc, manualOffline"
>
@@ -627,9 +627,9 @@
<!-- ..................................... -->
<!-- Binary distribution -->
<!-- ..................................... -->
-
+
<mkdir dir="${dist.bin.dir}" />
-
+
<!-- Copy txt-s -->
<copy todir="${dist.bin.dir}" includeEmptyDirs="no">
<fileset dir="." defaultexcludes="no">
@@ -656,7 +656,7 @@
<!-- Copy documentation -->
<mkdir dir="${dist.bin.dir}/documentation" />
-
+
<!--
The US English Manual is the source of any translations and thus it's the
only one that is guaranteed to be up to date when doing the release, so we
@@ -668,7 +668,7 @@
<copy todir="${dist.bin.dir}/documentation/_html/api" includeEmptyDirs="no">
<fileset dir="build/api" />
</copy>
-
+
<u:packageAndSignDist
srcDir="${dist.bin.dir}/.."
archiveNameWithoutExt="${dist.archiveBaseName}-bin"
@@ -677,7 +677,7 @@
<!-- ..................................... -->
<!-- Source distribution -->
<!-- ..................................... -->
-
+
<mkdir dir="${dist.src.dir}" />
<!-- Copy extensionless files: -->
@@ -694,7 +694,7 @@
token="{version}"
value="${version}"
/>
-
+
<copy todir="${dist.src.dir}" includeEmptyDirs="no">
<fileset dir="." defaultexcludes="no">
<exclude name="**/*.bak" />
@@ -709,7 +709,7 @@
<include name="rat-excludes" />
</fileset>
</copy>
-
+
<u:packageAndSignDist
srcDir="${dist.src.dir}/.."
archiveNameWithoutExt="${dist.archiveBaseName}-src"
@@ -740,7 +740,7 @@
<arg value="--detach-sig" />
<arg value="${archive.gzip}" />
</exec>
-
+
<echo>*** Signature verification: ***</echo>
<exec executable="${gpgCommand}" failonerror="true">
<arg value="--verify" />
@@ -757,12 +757,12 @@
<equals arg1="y" arg2="${signatureGood}"/>
</condition>
<fail unless="signatureGood.y" message="Task aborted by user." />
-
+
<echo>Creating checksum file for "${archive.gzip}"...</echo>
<checksum file="${archive.gzip}" fileext=".sha512" algorithm="SHA-512" forceOverwrite="yes" />
</sequential>
</macrodef>
-
+
<target name="maven-pom">
<echo file="build/pom.xml"><![CDATA[<?xml version="1.0" encoding="utf-8"?>
<!--
@@ -773,9 +773,9 @@
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
@@ -783,24 +783,24 @@
specific language governing permissions and limitations
under the License.
-->
-
+
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
-
+
<parent>
<groupId>org.apache</groupId>
<artifactId>apache</artifactId>
<version>17</version>
</parent>
-
+
<groupId>${mavenGroupId}</groupId>
<artifactId>${mavenArtifactId}</artifactId>
<version>${mavenVersion}</version>
-
+
<packaging>jar</packaging>
-
+
<name>Apache FreeMarker</name>
<description>
Google App Engine compliant variation of FreeMarker.
@@ -811,15 +811,15 @@
<name>Apache Software Foundation</name>
<url>http://apache.org</url>
</organization>
-
+
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
- <distribution>repo</distribution>
+ <distribution>repo</distribution>
</license>
</licenses>
-
+
<scm>
<connection>scm:git:https://git-wip-us.apache.org/repos/asf/freemarker.git</connection>
<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/freemarker.git</developerConnection>
@@ -852,7 +852,7 @@
<post>private@freemarker.apache.org</post>
</mailingList>
</mailingLists>
-
+
<dependencies>
<!-- no required dependencies -->
</dependencies>
@@ -922,7 +922,7 @@
<equals arg1="y" arg2="${mavenUpload.answer}"/>
</condition>
<fail unless="mavenUpload.yes" message="Task aborted by user." />
-
+
<!-- Sign and deploy the main artifact -->
<exec executable="${mvnCommand}" failonerror="true">
<arg value="org.apache.maven.plugins:maven-gpg-plugin:1.6:sign-and-deploy-file" />
@@ -938,7 +938,7 @@
<arg value="-Djavadoc=build/maven-javadoc-attachment.jar" />
<arg value="-Pgpg" />
</exec>
-
+
<echo>*****************************************************************</echo>
<echo>Check the above lines for any Maven errors!</echo>
<echo>Now you need to close and maybe release the staged repo on</echo>
@@ -963,7 +963,7 @@
<!-- ================================================================== -->
<!-- Dependency management -->
<!-- ================================================================== -->
-
+
<target name="report-deps"
description="Creates a HTML document that summarizes the dependencies."
>
@@ -971,15 +971,15 @@
<ivy:resolve />
<ivy:report todir="build/deps-report" />
</target>
-
+
<target name="ide-dependencies" depends="jar"
description="If your IDE has no Ivy support, this generates ide-lib/*.jar for it">
<mkdir dir="ide-dependencies" />
- <delete includeEmptyDirs="true">
- <fileset dir="ide-dependencies">
- <include name="*/**" />
- </fileset>
- </delete>
+ <delete includeEmptyDirs="true">
+ <fileset dir="ide-dependencies">
+ <include name="*/**" />
+ </fileset>
+ </delete>
<ivy:retrieve conf="IDE" pattern="ide-dependencies/[artifact]-[revision].[ext]" />
<!--
@@ -997,6 +997,33 @@
-->Do not edit the files in this directory! They are extracted from freemarker.jar as part of
<!--
-->the ide-dependencies Ant task, because Eclipse OSGi support expects them to be here.<!--
--></echo>
+
+ <!--
+ Add bndtools support (bnd workspace model), which requires a bnd.bnd file to be present.
+ See https://bndtools.org/concepts.html#workspacerepositoriesproject-model for more information.
+ We copy the existing osgi.bnd to bnd.bnd and replace the variables
+ with values from this build.xml and version.properties
+ The resulting bnd.bnd in the project root is only consumed by Eclipse
+ if the bndtools plugin is installed.
+ bnd.bnd is also in .gitignore, to avoid accidental commits.
+ -->
+ <filter token="moduleOrg" value="${moduleOrg}" />
+ <filter token="moduleName" value="${moduleName}" />
+ <filter filtersfile="build/classes/freemarker/version.properties" />
+
+ <copy
+ file="osgi.bnd"
+ tofile="bnd.bnd"
+ filtering="true"
+ overwrite="true"
+ >
+ <filterchain>
+ <expandproperties />
+ </filterchain>
+ </copy>
+
+
+
</target>
<!-- ================================================================== -->
@@ -1009,8 +1036,8 @@
uri="antlib:org.apache.rat.anttasks"
resource="org/apache/rat/anttasks/antlib.xml"
classpathref="ivy.dep"
- />
-
+ />
+
<rat:report reportFile="build/rat-report-root.txt">
<fileset dir="" excludesfile="rat-excludes" />
</rat:report>
@@ -1024,5 +1051,5 @@
-->Rat reports were written into build/rat-report-*.txt<!--
--></echo>
</target>
-
+
</project>
diff --git a/src/main/java/freemarker/cache/TemplateCache.java b/src/main/java/freemarker/cache/TemplateCache.java
index be19aee..77c6957 100644
--- a/src/main/java/freemarker/cache/TemplateCache.java
+++ b/src/main/java/freemarker/cache/TemplateCache.java
@@ -40,6 +40,7 @@
import freemarker.template.Template;
import freemarker.template.TemplateNotFoundException;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.UndeclaredThrowableException;
@@ -279,7 +280,7 @@
} catch (MalformedTemplateNameException e) {
// If we don't have to emulate backward compatible behavior, then just rethrow it:
if (templateNameFormat != TemplateNameFormat.DEFAULT_2_3_0
- || config.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_4_0) {
+ || config.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_4_0) {
throw e;
}
return new MaybeMissingTemplate(null, e);
@@ -795,7 +796,7 @@
private Object modifyForConfIcI(Object templateSource) {
if (templateSource == null) return null;
- if (config.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+ if (config.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_21) {
return templateSource;
}
diff --git a/src/main/java/freemarker/core/APINotSupportedTemplateException.java b/src/main/java/freemarker/core/APINotSupportedTemplateException.java
index f21fa0e..c706581 100644
--- a/src/main/java/freemarker/core/APINotSupportedTemplateException.java
+++ b/src/main/java/freemarker/core/APINotSupportedTemplateException.java
@@ -25,7 +25,7 @@
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
/**
* Thrown when {@code ?api} is not supported by a value.
@@ -56,7 +56,7 @@
desc.tip("In the FreeMarker configuration, \"", Configurable.OBJECT_WRAPPER_KEY,
"\" is a DefaultObjectWrapper with its \"useAdaptersForContainers\" property set to "
+ "false. Setting it to true might solves this problem.");
- if (dow.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22) {
+ if (dow.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22) {
desc.tip("Setting DefaultObjectWrapper's \"incompatibleImprovements\" to 2.3.22 or higher will "
+ "change the default value of \"useAdaptersForContainers\" to true.");
}
diff --git a/src/main/java/freemarker/core/AbstractJSONLikeFormat.java b/src/main/java/freemarker/core/AbstractJSONLikeFormat.java
new file mode 100644
index 0000000..7f305ff
--- /dev/null
+++ b/src/main/java/freemarker/core/AbstractJSONLikeFormat.java
@@ -0,0 +1,77 @@
+/*
+ * 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.core;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+/**
+ * Defines the methods in {@link CFormat} that are the same for all JSON-like languages.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
+ * The class itself and some members are exposed as they are needed for configuring FreeMarker.
+ *
+ * @since 2.3.32
+ */
+public abstract class AbstractJSONLikeFormat extends CFormat {
+ private static final TemplateNumberFormat TEMPLATE_NUMBER_FORMAT = new CTemplateNumberFormat(
+ "Infinity", "-Infinity", "NaN",
+ "Infinity", "-Infinity", "NaN");
+
+ private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+ = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+ static {
+ DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
+ symbols.setInfinity("Infinity");
+ symbols.setNaN("NaN");
+ LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalFormatSymbols(symbols);
+ }
+
+ // Visibility is not "protected" to avoid external implementations while this class is experimental.
+ AbstractJSONLikeFormat() {
+ }
+
+ @Override
+ String getTrueString() {
+ return "true";
+ }
+
+ @Override
+ String getFalseString() {
+ return "false";
+ }
+
+ @Override
+ final String getNullString() {
+ return "null";
+ }
+
+ @Override
+ final TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+ return TEMPLATE_NUMBER_FORMAT;
+ }
+
+ @Override
+ NumberFormat getLegacyNumberFormat(Environment env) {
+ return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+ }
+}
diff --git a/src/main/java/freemarker/core/AddConcatExpression.java b/src/main/java/freemarker/core/AddConcatExpression.java
index a4bfb9f..f02be12 100644
--- a/src/main/java/freemarker/core/AddConcatExpression.java
+++ b/src/main/java/freemarker/core/AddConcatExpression.java
@@ -35,7 +35,7 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* An operator for the + operator. Note that this is treated
@@ -274,7 +274,7 @@
throws TemplateModelException {
if (keys == null) {
HashSet keySet = new HashSet();
- SimpleSequence keySeq = new SimpleSequence(32, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence keySeq = new SimpleSequence(32, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
addKeys(keySet, keySeq, (TemplateHashModelEx) this.left);
addKeys(keySet, keySeq, (TemplateHashModelEx) this.right);
keys = new CollectionAndSequence(keySeq);
@@ -297,7 +297,7 @@
private void initValues()
throws TemplateModelException {
if (values == null) {
- SimpleSequence seq = new SimpleSequence(size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence seq = new SimpleSequence(size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
// Note: size() invokes initKeys() if needed.
int ln = keys.size();
diff --git a/src/main/java/freemarker/core/ArithmeticEngine.java b/src/main/java/freemarker/core/ArithmeticEngine.java
index 5492b7a..dc946d2 100644
--- a/src/main/java/freemarker/core/ArithmeticEngine.java
+++ b/src/main/java/freemarker/core/ArithmeticEngine.java
@@ -116,7 +116,7 @@
@Override
public int compareNumbers(Number first, Number second) {
// We try to find the result based on the sign (+/-/0) first, because:
- // - It's much faster than converting to BigDecial, and comparing to 0 is the most common comparison.
+ // - It's much faster than converting to BigDecimal, and comparing to 0 is the most common comparison.
// - It doesn't require any type conversions, and thus things like "Infinity > 0" won't fail.
int firstSignum = NumberUtil.getSignum(first);
int secondSignum = NumberUtil.getSignum(second);
@@ -126,7 +126,7 @@
return 0;
} else {
// The most common case is comparing values of the same type. As BigDecimal can represent all of these
- // with loseless round-trip (i.e., converting to BigDecimal and then back the original type gives the
+ // with lossless round-trip (i.e., converting to BigDecimal and then back the original type gives the
// original value), we can avoid conversion to BigDecimal without changing the result.
if (first.getClass() == second.getClass()) {
// Bit of optimization for this is a very common case:
@@ -623,7 +623,7 @@
return new BigDecimal(num.toString());
} catch (NumberFormatException e) {
if (NumberUtil.isInfinite(num)) {
- throw new NumberFormatException("It's impossible to convert an infinte value ("
+ throw new NumberFormatException("It's impossible to convert an infinite value ("
+ num.getClass().getSimpleName() + " " + num + ") to BigDecimal.");
}
// The exception message is useless, so we add a new one:
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index fcea193..708c562 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -85,8 +85,8 @@
static final Set<String> CAMEL_CASE_NAMES = new TreeSet<>();
static final Set<String> SNAKE_CASE_NAMES = new TreeSet<>();
- static final int NUMBER_OF_BIS = 291;
- static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
+ static final int NUMBER_OF_BIS = 296;
+ static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap<>(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
static final String BI_NAME_SNAKE_CASE_WITH_ARGS = "with_args";
static final String BI_NAME_CAMEL_CASE_WITH_ARGS = "withArgs";
@@ -103,6 +103,7 @@
putBI("boolean", new BuiltInsForStringsMisc.booleanBI());
putBI("byte", new byteBI());
putBI("c", new BuiltInsForMultipleTypes.cBI());
+ putBI("cn", new BuiltInsForMultipleTypes.cnBI());
putBI("cap_first", "capFirst", new BuiltInsForStringsBasic.cap_firstBI());
putBI("capitalize", new BuiltInsForStringsBasic.capitalizeBI());
putBI("ceiling", new ceilingBI());
@@ -247,6 +248,7 @@
putBI("long", new longBI());
putBI("lower_abc", "lowerAbc", new BuiltInsForNumbers.lower_abcBI());
putBI("lower_case", "lowerCase", new BuiltInsForStringsBasic.lower_caseBI());
+ putBI("c_lower_case", "cLowerCase", new BuiltInsForStringsBasic.c_lower_caseBI());
putBI("map", new BuiltInsForSequences.mapBI());
putBI("namespace", new BuiltInsForMultipleTypes.namespaceBI());
putBI("new", new NewBI());
@@ -300,6 +302,7 @@
putBI("uncap_first", "uncapFirst", new BuiltInsForStringsBasic.uncap_firstBI());
putBI("upper_abc", "upperAbc", new BuiltInsForNumbers.upper_abcBI());
putBI("upper_case", "upperCase", new BuiltInsForStringsBasic.upper_caseBI());
+ putBI("c_upper_case", "cUpperCase", new BuiltInsForStringsBasic.c_upper_caseBI());
putBI("url", new BuiltInsForStringsEncoding.urlBI());
putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI());
putBI("values", new BuiltInsForHashes.valuesBI());
diff --git a/src/main/java/freemarker/core/BuiltInsForDates.java b/src/main/java/freemarker/core/BuiltInsForDates.java
index c4f660b..2a70266 100644
--- a/src/main/java/freemarker/core/BuiltInsForDates.java
+++ b/src/main/java/freemarker/core/BuiltInsForDates.java
@@ -33,6 +33,7 @@
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.DateUtil;
import freemarker.template.utility.UnrecognizedTimeZoneException;
@@ -206,7 +207,7 @@
} else {
// java.sql.Time values meant to carry calendar field values only, so we don't show offset for them.
return !(date instanceof java.sql.Time
- && _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _TemplateAPI.VERSION_INT_2_3_21);
+ && _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _VersionInts.V_2_3_21);
}
}
diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
index fbd528c..e2eb273 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -48,6 +48,7 @@
import freemarker.template.TemplateSequenceModel;
import freemarker.template.TemplateTransformModel;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.NumberUtil;
/**
@@ -55,86 +56,52 @@
*/
class BuiltInsForMultipleTypes {
- static class cBI extends AbstractCBI implements ICIChainMember {
-
- static class BIBeforeICI2d3d21 extends AbstractCBI {
-
- @Override
- protected TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException {
- Number num = EvalUtil.modelToNumber((TemplateNumberModel) model, target);
- if (num instanceof Integer || num instanceof Long) {
- // Accelerate these fairly common cases
- return new SimpleScalar(num.toString());
- } else {
- return new SimpleScalar(env.getCNumberFormat().format(num));
- }
- }
-
+ static class cBI extends AbstractCLikeBI {
+ final protected String formatNull(Environment env) throws InvalidReferenceException {
+ throw InvalidReferenceException.getInstance(target, env);
}
-
- private final BIBeforeICI2d3d21 prevICIObj = new BIBeforeICI2d3d21();
+ }
+
+ static class cnBI extends AbstractCLikeBI {
+ final protected String formatNull(Environment env) {
+ return env.getCFormat().getNullString();
+ }
+ }
+
+ private static abstract class AbstractCLikeBI extends BuiltIn {
@Override
- TemplateModel _eval(Environment env) throws TemplateException {
- TemplateModel model = target.eval(env);
+ final TemplateModel _eval(Environment env) throws TemplateException {
+ final String result;
+ final TemplateModel model = target.eval(env);
if (model instanceof TemplateNumberModel) {
- return formatNumber(env, model);
+ TemplateNumberFormat cTemplateNumberFormat = env.getCTemplateNumberFormat();
+ try {
+ result = cTemplateNumberFormat.formatToPlainText((TemplateNumberModel) model);
+ } catch (TemplateValueFormatException e) {
+ throw _MessageUtil.newCantFormatNumberException(cTemplateNumberFormat, target, e, false);
+ }
} else if (model instanceof TemplateBooleanModel) {
- return new SimpleScalar(((TemplateBooleanModel) model).getAsBoolean()
- ? MiscUtil.C_TRUE : MiscUtil.C_FALSE);
+ boolean b = ((TemplateBooleanModel) model).getAsBoolean();
+ CFormat cFormat = env.getCFormat();
+ result = b ? cFormat.getTrueString() : cFormat.getFalseString();
+ } else if (model instanceof TemplateScalarModel) {
+ String s = EvalUtil.modelToString((TemplateScalarModel) model, target, env);
+ result = env.getCFormat().formatString(s, env);
+ } else if (model == null) {
+ result = formatNull(env);
} else {
throw new UnexpectedTypeException(
target, model,
- "number or boolean", new Class[] { TemplateNumberModel.class, TemplateBooleanModel.class },
+ "number, boolean, or string",
+ new Class[] { TemplateNumberModel.class, TemplateBooleanModel.class, TemplateScalarModel.class },
env);
}
+ return new SimpleScalar(result);
}
- @Override
- protected TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException {
- Number num = EvalUtil.modelToNumber((TemplateNumberModel) model, target);
- if (num instanceof Integer || num instanceof Long) {
- // Accelerate these fairly common cases
- return new SimpleScalar(num.toString());
- } else if (num instanceof Double) {
- double n = num.doubleValue();
- if (n == Double.POSITIVE_INFINITY) {
- return new SimpleScalar("INF");
- }
- if (n == Double.NEGATIVE_INFINITY) {
- return new SimpleScalar("-INF");
- }
- if (Double.isNaN(n)) {
- return new SimpleScalar("NaN");
- }
- // Deliberately falls through
- } else if (num instanceof Float) {
- float n = num.floatValue();
- if (n == Float.POSITIVE_INFINITY) {
- return new SimpleScalar("INF");
- }
- if (n == Float.NEGATIVE_INFINITY) {
- return new SimpleScalar("-INF");
- }
- if (Float.isNaN(n)) {
- return new SimpleScalar("NaN");
- }
- // Deliberately falls through
- }
-
- return new SimpleScalar(env.getCNumberFormat().format(num));
- }
+ protected abstract String formatNull(Environment env) throws InvalidReferenceException;
- @Override
- public int getMinimumICIVersion() {
- return _TemplateAPI.VERSION_INT_2_3_21;
- }
-
- @Override
- public Object getPreviousICIChainMember() {
- return prevICIObj;
- }
-
}
static class dateBI extends BuiltIn {
@@ -355,7 +322,7 @@
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
- && (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _TemplateAPI.VERSION_INT_2_3_21
+ && (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _VersionInts.V_2_3_21
// These implement TemplateSequenceModel, yet they can't be #list-ed:
|| !(tm instanceof SimpleMethodModel || tm instanceof OverloadedMethodsModel))
? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
@@ -792,26 +759,4 @@
// Can't be instantiated
private BuiltInsForMultipleTypes() { }
- static abstract class AbstractCBI extends BuiltIn {
-
- @Override
- TemplateModel _eval(Environment env) throws TemplateException {
- TemplateModel model = target.eval(env);
- if (model instanceof TemplateNumberModel) {
- return formatNumber(env, model);
- } else if (model instanceof TemplateBooleanModel) {
- return new SimpleScalar(((TemplateBooleanModel) model).getAsBoolean()
- ? MiscUtil.C_TRUE : MiscUtil.C_FALSE);
- } else {
- throw new UnexpectedTypeException(
- target, model,
- "number or boolean", new Class[] { TemplateNumberModel.class, TemplateBooleanModel.class },
- env);
- }
- }
-
- protected abstract TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException;
-
- }
-
}
diff --git a/src/main/java/freemarker/core/BuiltInsForNodes.java b/src/main/java/freemarker/core/BuiltInsForNodes.java
index 71e76bd..ff75b6e 100644
--- a/src/main/java/freemarker/core/BuiltInsForNodes.java
+++ b/src/main/java/freemarker/core/BuiltInsForNodes.java
@@ -30,7 +30,7 @@
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateNodeModelEx;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* A holder for builtins that operate exclusively on (XML-)node left-hand value.
@@ -123,7 +123,7 @@
private Environment env;
AncestorSequence(Environment env) {
- super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
this.env = env;
}
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index b80f42e..3d2b9fc 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -45,7 +45,7 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.Constants;
import freemarker.template.utility.StringUtil;
@@ -889,8 +889,8 @@
coll instanceof TemplateCollectionModelEx
? new SimpleSequence(
((TemplateCollectionModelEx) coll).size(),
- _TemplateAPI.SAFE_OBJECT_WRAPPER)
- : new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ _ObjectWrappers.SAFE_OBJECT_WRAPPER)
+ : new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (TemplateModelIterator iter = coll.iterator(); iter.hasNext(); ) {
seq.add(iter.next());
}
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
index 4ab8c40..ce69d42 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
@@ -20,6 +20,7 @@
package freemarker.core;
import java.util.List;
+import java.util.Locale;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -35,7 +36,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.StringUtil;
class BuiltInsForStringsBasic {
@@ -435,7 +436,14 @@
TemplateModel calculateResult(String s, Environment env) {
return new SimpleScalar(s.toLowerCase(env.getLocale()));
}
- }
+ }
+
+ static class c_lower_caseBI extends BuiltInForString {
+ @Override
+ TemplateModel calculateResult(String s, Environment env) {
+ return new SimpleScalar(s.toLowerCase(Locale.ROOT));
+ }
+ }
static class padBI extends BuiltInForString {
@@ -830,10 +838,17 @@
}
}
+ static class c_upper_caseBI extends BuiltInForString {
+ @Override
+ TemplateModel calculateResult(String s, Environment env) {
+ return new SimpleScalar(s.toUpperCase(Locale.ROOT));
+ }
+ }
+
static class word_listBI extends BuiltInForString {
@Override
TemplateModel calculateResult(String s, Environment env) {
- SimpleSequence result = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence result = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
StringTokenizer st = new StringTokenizer(s);
while (st.hasMoreTokens()) {
result.add(st.nextToken());
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
index f600135..59dc3fe 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
@@ -28,7 +28,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.StringUtil;
class BuiltInsForStringsEncoding {
@@ -51,7 +51,7 @@
@Override
public int getMinimumICIVersion() {
- return _TemplateAPI.VERSION_INT_2_3_20;
+ return _VersionInts.V_2_3_20;
}
@Override
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java b/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
index eddc97c..d8be40b 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
@@ -35,7 +35,7 @@
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.StringUtil;
@@ -145,7 +145,7 @@
MatchWithGroups(String input, Matcher matcher) {
matchedInputPart = input.substring(matcher.start(), matcher.end());
final int grpCount = matcher.groupCount() + 1;
- groupsSeq = new SimpleSequence(grpCount, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ groupsSeq = new SimpleSequence(grpCount, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (int i = 0; i < grpCount; i++) {
groupsSeq.add(matcher.group(i));
}
diff --git a/src/main/java/freemarker/core/BuiltinVariable.java b/src/main/java/freemarker/core/BuiltinVariable.java
index ccc9d0b..b8f06a0 100644
--- a/src/main/java/freemarker/core/BuiltinVariable.java
+++ b/src/main/java/freemarker/core/BuiltinVariable.java
@@ -32,7 +32,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.StringUtil;
/**
@@ -222,7 +222,7 @@
// The behavior of env.getTemplate() was changed with IcI 2.3.22, but there was an unintended side effect
// of changing the behavior of .template_name, which was fixed with IcI 2.3.23. IcI 2.3.22 deliberately
// remains broken.
- return (env.getConfiguration().getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_23)
+ return (env.getConfiguration().getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_23)
? new SimpleScalar(env.getTemplate230().getName())
: new SimpleScalar(env.getTemplate().getName());
}
diff --git a/src/main/java/freemarker/core/CFormat.java b/src/main/java/freemarker/core/CFormat.java
new file mode 100644
index 0000000..1b92f60
--- /dev/null
+++ b/src/main/java/freemarker/core/CFormat.java
@@ -0,0 +1,82 @@
+/*
+ * 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.core;
+
+import java.text.NumberFormat;
+
+import freemarker.template.TemplateException;
+
+/**
+ * Defines a format (usually a computer language) that's used by the {@code c}, {@code cn} built-ins, and for the
+ * {@code "c"} and {@code "computer"} {@link Configurable#setNumberFormat(String) number_format}, and
+ * the {@code "c"} {@link Configurable#setBooleanFormat(String) boolean_format}.
+ * A {@link CFormat} currently defines how numbers, booleans, and strings are converted to text that defines a similar
+ * value in a certain computer language (or other computer-parsed syntax).
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
+ * The class itself and some members are exposed as they are needed for configuring FreeMarker.
+ *
+ * @since 2.3.32
+ */
+public abstract class CFormat {
+
+ // Visibility is not "protected" to avoid external implementations while this class is experimental.
+ CFormat() {
+ }
+
+ /**
+ * Gets/creates the number format to use; this is not always a cheap operation, so in case it will be called
+ * for many cases, the result should be cached or otherwise reused.
+ *
+ * <p>The returned object is typically not thread-safe. The implementation must ensure that if there's a singleton,
+ * which is mutable, or not thread-safe, then it's not returned, but a clone or copy of it. The caller of this
+ * method is not responsible for do any such cloning or copying.
+ */
+ abstract TemplateNumberFormat getTemplateNumberFormat(Environment env);
+
+ /**
+ * Similar to {@link #getTemplateNumberFormat(Environment)}, but only exist to serve the deprecated
+ * {@link Environment#getCNumberFormat()} method. We don't expect the result of the formatting to be the same as
+ * with the {@link TemplateNumberFormat}, but this method should make some effort to be similar.
+ *
+ * @deprecated Use {@link #getTemplateNumberFormat(Environment)} instead, except in
+ * {@link Environment#getCNumberFormat()}.
+ */
+ @Deprecated
+ abstract NumberFormat getLegacyNumberFormat(Environment env);
+
+ /**
+ * Format a {@link String} to a string literal.
+ *
+ * @param env
+ * Not {@code null}; is here mostly to be used to figure out escaping preferences (like based on
+ * {@link Environment#getOutputEncoding()}).
+ */
+ abstract String formatString(String s, Environment env) throws TemplateException;
+
+ abstract String getTrueString();
+
+ abstract String getFalseString();
+
+ abstract String getNullString();
+
+ public abstract String getName();
+}
diff --git a/src/main/java/freemarker/core/CTemplateNumberFormat.java b/src/main/java/freemarker/core/CTemplateNumberFormat.java
new file mode 100644
index 0000000..3df4177
--- /dev/null
+++ b/src/main/java/freemarker/core/CTemplateNumberFormat.java
@@ -0,0 +1,198 @@
+/*
+ * 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.core;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+
+/**
+ * The number format used by most {@link CFormat}-s starting from Incompatible Improvements
+ * {@link Configuration#VERSION_2_3_32}.
+ *
+ * <p>This {@link TemplateNumberFormat} implementation is thread-safe an immutable.
+ *
+ * <p>This is a lossless format, except that the number types are not kept. That is, the original number can always be
+ * exactly restored from the string representation, but only if you know what the type of the number was.
+ *
+ * @since 2.3.32
+ */
+final class CTemplateNumberFormat extends TemplateNumberFormat {
+ private static final float MAX_INCREMENT_1_FLOAT = 16777216f;
+ private static final double MAX_INCREMENT_1_DOUBLE = 9007199254740992d;
+
+ private final String doublePositiveInfinity;
+ private final String doubleNegativeInfinity;
+ private final String doubleNaN;
+ private final String floatPositiveInfinity;
+ private final String floatNegativeInfinity;
+ private final String floatNaN;
+
+ CTemplateNumberFormat(
+ String doublePositiveInfinity, String doubleNegativeInfinity, String doubleNaN,
+ String floatPositiveInfinity, String floatNegativeInfinity, String floatNaN) {
+ this.doublePositiveInfinity = doublePositiveInfinity;
+ this.doubleNegativeInfinity = doubleNegativeInfinity;
+ this.doubleNaN = doubleNaN;
+ this.floatPositiveInfinity = floatPositiveInfinity;
+ this.floatNegativeInfinity = floatNegativeInfinity;
+ this.floatNaN = floatNaN;
+ }
+
+ @Override
+ public String formatToPlainText(TemplateNumberModel numberModel) throws TemplateValueFormatException,
+ TemplateModelException {
+ Number num = TemplateFormatUtil.getNonNullNumber(numberModel);
+
+ if (num instanceof Integer || num instanceof Long) {
+ // Accelerate these fairly common cases
+ return num.toString();
+ } else if (num instanceof Double) {
+ double n = num.doubleValue();
+ if (n == Double.POSITIVE_INFINITY) {
+ return doublePositiveInfinity;
+ }
+ if (n == Double.NEGATIVE_INFINITY) {
+ return doubleNegativeInfinity;
+ }
+ if (Double.isNaN(n)) {
+ return doubleNaN;
+ }
+ if (Math.floor(n) == n) {
+ if (Math.abs(n) <= MAX_INCREMENT_1_DOUBLE) {
+ return Long.toString((long) n);
+ }
+ // We let this fall through to exponential form, despite that we certainly haven't reached the 100
+ // digits limit, because ulp > 1, so if this was an ID, its value is potentially corrupted anyway.
+ } else { // Non-whole number
+ double absN = Math.abs(n);
+ // The range where Double.toString already uses exponential form, but BigDecimal toString doesn't yet.
+ if (absN < 1E-3 && absN > 1E-7) {
+ // Always use BigDecimal.valueOf(n), not new BigDecimal(n)! For example, BigDecimal.valueOf(1.9) is
+ // just the BigDecimal 1.9, but new BigDecimal(1.9) is a very long BigDecimal starting with 1.899.
+ return BigDecimal.valueOf(n).toString();
+ }
+ // Avoid exponential form where we don't use it with BigDecimal-s. We don't worry about the 100 digits
+ // limit, as such big numbers are whole numbers.
+ if (absN >= 1E7) {
+ // No new BigDecimal(n)! See earlier why.
+ return BigDecimal.valueOf(n).toPlainString();
+ }
+ }
+ // Remove redundant ".0" so that result look the same as with BigDecimal
+ return removeRedundantDot0(Double.toString(n));
+ } else if (num instanceof Float) {
+ float n = num.floatValue();
+
+ if (n == Float.POSITIVE_INFINITY) {
+ return floatPositiveInfinity;
+ }
+ if (n == Float.NEGATIVE_INFINITY) {
+ return floatNegativeInfinity;
+ }
+ if (Float.isNaN(n)) {
+ return floatNaN;
+ }
+ if (Math.floor(n) == n) {
+ if (Math.abs(n) <= MAX_INCREMENT_1_FLOAT) {
+ return Long.toString((long) n);
+ }
+ // We let this fall through to exponential form, despite that we certainly haven't reached the 100
+ // digits limit, because ulp > 1, so if this was an ID, its value is potentially corrupted anyway.
+ } else { // Non-whole number
+ float absN = Math.abs(n);
+ // The range where Float.toString already uses exponential form, but BigDecimal toString doesn't yet.
+ if (absN < 1E-3f && absN > 1E-7f) {
+ // No new BigDecimal(n); see earlier why. But there's no BigDecimal.valueOf(float), so we do this.
+ return new BigDecimal(num.toString()).toString();
+ }
+ // Unlike with double, we don't need an absN >= 1E7 branch, since those number are whole with float.
+ }
+ // Remove redundant ".0" so that result look the same as with BigDecimal
+ return removeRedundantDot0(Float.toString(n));
+ } else if (num instanceof BigInteger) {
+ return num.toString();
+ } else if (num instanceof BigDecimal) {
+ BigDecimal bd = ((BigDecimal) num).stripTrailingZeros();
+ int scale = bd.scale();
+ if (scale <= 0) {
+ // A whole number. Maybe a long ID in a database or other system, and for those exponential form is not
+ // expected generally, so we avoid that. But then, it becomes too easy to write something like
+ // 1e1000000000000 and kill the server with a terra byte long rendering of the number, so for lengths
+ // that realistically aren't ID-s or such, we use exponential format after all:
+ if (scale <= -100) {
+ return bd.toString(); // Will give exponential form for this scale
+ }
+ return bd.toPlainString(); // Never gives exponential form
+ }
+ // `db` is not a whole number. Note that `bd` is already normalized to not have trailing zeroes.
+ return bd.toString(); // Gives exponential form of the absolute value of the number is less than 1E-7
+ } else {
+ // We don't know what this is, but as we want the format to be lossless, this is our best guess.
+ return num.toString();
+ }
+ }
+
+ /**
+ * Like "1.0" to "1", and "1.0E-7" to "1E-7". Designed to work on Float/Double.toString() result only.
+ */
+ private static String removeRedundantDot0(String s) {
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ char c = s.charAt(i);
+ if (c == '.') {
+ i++;
+ if (s.charAt(i) == '0') {
+ i++;
+ if (i == len) {
+ return s.substring(0, len - 2);
+ }
+ if (s.charAt(i) == 'E') {
+ char[] result = new char[s.length() - 2];
+ int dst = 0;
+ for (int src = 0; src < i - 2; src++) {
+ result[dst++] = s.charAt(src);
+ }
+ for (int src = i; src < len; src++) {
+ result[dst++] = s.charAt(src);
+ }
+ return String.valueOf(result);
+ }
+ }
+ break;
+ }
+ }
+ return s;
+ }
+
+ @Override
+ public boolean isLocaleBound() {
+ return false;
+ }
+
+ @Override
+ public String getDescription() {
+ return "c";
+ }
+
+}
diff --git a/src/main/java/freemarker/core/CommonMarkupOutputFormat.java b/src/main/java/freemarker/core/CommonMarkupOutputFormat.java
index c9200bf..ff1fe78 100644
--- a/src/main/java/freemarker/core/CommonMarkupOutputFormat.java
+++ b/src/main/java/freemarker/core/CommonMarkupOutputFormat.java
@@ -117,6 +117,16 @@
/**
* Creates a new {@link CommonTemplateMarkupOutputModel} that's bound to this {@link OutputFormat} instance.
+ * When this is called from {@link CommonMarkupOutputFormat}, exactly one of the parameters will be
+ * non-{@code null}.
+ *
+ * @param plainTextContent
+ * {@code null} if the content is markup that possibly can be described as plain text without loss.
+ * @param markupContent
+ * Typically will be {@code null} if {@code plainTextContent} is not {@code null}. While the plain text always
+ * can be converted to markup via {@link #escapePlainText(String)}, it's up to the implementation if it wants
+ * to do that now and store the markup, or later on demand (like each time when
+ * {@link #getMarkupString(CommonTemplateMarkupOutputModel)} is called).
*/
protected abstract MO newTemplateMarkupOutputModel(String plainTextContent, String markupContent)
throws TemplateModelException;
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 8b2e0e0..bf6e3aa 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -65,6 +65,8 @@
import freemarker.template.TemplateModel;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
+import freemarker.template.utility.CollectionUtils;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.StringUtil;
@@ -81,7 +83,7 @@
* object.
*/
public class Configurable {
- static final String C_TRUE_FALSE = "true,false";
+ static final String BOOLEAN_FORMAT_LEGACY_DEFAULT = "true,false";
static final String C_FORMAT_STRING = "c";
private static final String NULL = "null";
@@ -95,7 +97,14 @@
public static final String LOCALE_KEY_CAMEL_CASE = "locale";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String LOCALE_KEY = LOCALE_KEY_SNAKE_CASE;
-
+
+ /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+ public static final String C_FORMAT_KEY_SNAKE_CASE = "c_format";
+ /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+ public static final String C_FORMAT_KEY_CAMEL_CASE = "cFormat";
+ /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+ public static final String C_FORMAT_KEY = C_FORMAT_KEY_SNAKE_CASE;
+
/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String NUMBER_FORMAT_KEY_SNAKE_CASE = "number_format";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
@@ -305,6 +314,7 @@
AUTO_IMPORT_KEY_SNAKE_CASE,
AUTO_INCLUDE_KEY_SNAKE_CASE,
BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+ C_FORMAT_KEY_SNAKE_CASE,
CLASSIC_COMPATIBLE_KEY_SNAKE_CASE,
CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
@@ -338,6 +348,7 @@
AUTO_IMPORT_KEY_CAMEL_CASE,
AUTO_INCLUDE_KEY_CAMEL_CASE,
BOOLEAN_FORMAT_KEY_CAMEL_CASE,
+ C_FORMAT_KEY_CAMEL_CASE,
CLASSIC_COMPATIBLE_KEY_CAMEL_CASE,
CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
@@ -367,6 +378,7 @@
private HashMap<Object, Object> customAttributes;
private Locale locale;
+ private CFormat cFormat;
private String numberFormat;
private String timeFormat;
private String dateFormat;
@@ -375,8 +387,6 @@
private TimeZone sqlDataAndTimeTimeZone;
private boolean sqlDataAndTimeTimeZoneSet;
private String booleanFormat;
- private String trueStringValue; // deduced from booleanFormat
- private String falseStringValue; // deduced from booleanFormat
private Integer classicCompatible;
private TemplateExceptionHandler templateExceptionHandler;
private AttemptExceptionReporter attemptExceptionReporter;
@@ -424,7 +434,7 @@
locale = _TemplateAPI.getDefaultLocale();
properties.setProperty(LOCALE_KEY, locale.toString());
-
+
timeZone = _TemplateAPI.getDefaultTimeZone();
properties.setProperty(TIME_ZONE_KEY, timeZone.getID());
@@ -442,7 +452,9 @@
dateTimeFormat = "";
properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat);
-
+
+ cFormat = _TemplateAPI.getDefaultCFormat(incompatibleImprovements);
+
classicCompatible = Integer.valueOf(0);
properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
@@ -480,7 +492,7 @@
// outputEncoding and urlEscapingCharset defaults to null,
// which means "not specified"
- setBooleanFormat(C_TRUE_FALSE);
+ setBooleanFormat(BOOLEAN_FORMAT_LEGACY_DEFAULT);
customAttributes = new HashMap();
@@ -677,12 +689,47 @@
/**
* Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
*
+ * @since 2.3.32
+ */
+ public boolean isCFormatSet() {
+ return cFormat != null;
+ }
+
+ /**
+ * Sets the format (usually a computer language) used for {@code ?c}, {@code ?cn}, and for the
+ * {@code "c"} ({@code "computer"} before 2.3.32) {@link #setNumberFormat(String) number_format}, and the
+ * {@code "c"} {@link #setBooleanFormat(String) boolean_format}.
+ *
+ * <p>The default value depends on {@link Configuration#Configuration(Version) incompatible_improvements}.
+ * If that's 2.3.32 or higher, then it's {@link JavaScriptOrJSONCFormat#INSTANCE "JavaScript or JSON"},
+ * otherwise it's {@link LegacyCFormat#INSTANCE "legacy"}.
+ *
+ * @since 2.3.32
+ */
+ public void setCFormat(CFormat cFormat) {
+ NullArgumentException.check("cFormat", cFormat);
+ this.cFormat = cFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setCFormat(CFormat)}. Not {@code null}.
+ *
+ * @since 2.3.32
+ */
+ public CFormat getCFormat() {
+ return cFormat != null ? cFormat : parent.getCFormat();
+ }
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
* @since 2.3.24
*/
public boolean isLocaleSet() {
return locale != null;
}
-
+
+
/**
* Sets the time zone to use when formatting date/time values.
* Defaults to the system time zone ({@link TimeZone#getDefault()}), regardless of the "locale" FreeMarker setting,
@@ -809,12 +856,18 @@
}
/**
- * Sets the default number format used to convert numbers to strings. Currently, this is one of these:
+ * Sets the number format used to convert numbers to strings. Currently, this is one of these:
* <ul>
- * <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
+ * <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}. This is the
+ * default.</li>
+ * <li>{@code "c"} (recognized since 2.3.32): The number format used by FTL's {@code c} built-in (like in
+ * {@code someNumber?c}). So with this <code>${someNumber}</code> will output the same as
+ * <code>${someNumber?c}</code>. This should only be used if the template solely generates source code,
+ * configuration file, or other content that's not read by normal users. If the template contains parts that's
+ * read by normal users (like typical a web page), you are not supposed to use this.</li>
+ * <li>{@code "computer"}: The old (deprecated) name for {@code "c"}. Recognized by all FreeMarker versions.</li>
* <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
* <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
- * <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
* <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax is extended by FreeMarker
* so that you can specify options like the rounding mode and the symbols used after a 2nd semicolon. For
* example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} will format numbers like {@code ",000"}
@@ -829,8 +882,7 @@
* <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
* </li>
* </ul>
- *
- *
+ *
* <p>Defaults to <tt>"number"</tt>.
*/
public void setNumberFormat(String numberFormat) {
@@ -981,16 +1033,38 @@
* only influenced the result of {@code myBool?string}.
*/
public void setBooleanFormat(String booleanFormat) {
+ validateBooleanFormat(booleanFormat);
+ this.booleanFormat = booleanFormat;
+ properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
+ }
+
+ /**
+ * @throws IllegalArgumentException If the format string has unrecognized format
+ */
+ private static void validateBooleanFormat(String booleanFormat) {
+ parseOrValidateBooleanFormat(booleanFormat, true);
+ }
+
+ /**
+ * @return {@code null} for legacy default (not set in effect), empty array if the {@link CFormat} should be used,
+ * and an array of {@code [trueString, falseString]} otherwise.
+ *
+ * @throws IllegalArgumentException If the format string has unrecognized format
+ */
+ static String[] parseBooleanFormat(String booleanFormat) {
+ return parseOrValidateBooleanFormat(booleanFormat, false);
+ }
+
+ private static String[] parseOrValidateBooleanFormat(String booleanFormat, boolean validateOnly) {
NullArgumentException.check("booleanFormat", booleanFormat);
- if (booleanFormat.equals(C_TRUE_FALSE)) {
- // C_TRUE_FALSE is the default for BC, but it's not a good default for human audience formatting, so we
- // pretend that it wasn't set.
- trueStringValue = null;
- falseStringValue = null;
- } else if (booleanFormat.equals(C_FORMAT_STRING)) {
- trueStringValue = MiscUtil.C_TRUE;
- falseStringValue = MiscUtil.C_FALSE;
+ if (booleanFormat.equals(C_FORMAT_STRING)) {
+ if (validateOnly) {
+ return null;
+ }
+ return CollectionUtils.EMPTY_STRING_ARRAY;
+ } else if (booleanFormat.equals(BOOLEAN_FORMAT_LEGACY_DEFAULT)) {
+ return null;
} else {
int commaIdx = booleanFormat.indexOf(',');
if (commaIdx == -1) {
@@ -999,12 +1073,14 @@
"or it must be \"" + C_FORMAT_STRING + "\", but it was " +
StringUtil.jQuote(booleanFormat) + ".");
}
- trueStringValue = booleanFormat.substring(0, commaIdx);
- falseStringValue = booleanFormat.substring(commaIdx + 1);
+ if (validateOnly) {
+ return null;
+ }
+ return new String[] {
+ booleanFormat.substring(0, commaIdx),
+ booleanFormat.substring(commaIdx + 1)
+ };
}
-
- this.booleanFormat = booleanFormat;
- properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
}
/**
@@ -1013,7 +1089,7 @@
public String getBooleanFormat() {
return booleanFormat != null ? booleanFormat : parent.getBooleanFormat();
}
-
+
/**
* Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
*
@@ -1022,83 +1098,6 @@
public boolean isBooleanFormatSet() {
return booleanFormat != null;
}
-
- String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
- if (value) {
- String s = getTrueStringValue();
- if (s == null) {
- if (fallbackToTrueFalse) {
- return MiscUtil.C_TRUE;
- } else {
- throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
- }
- } else {
- return s;
- }
- } else {
- String s = getFalseStringValue();
- if (s == null) {
- if (fallbackToTrueFalse) {
- return MiscUtil.C_FALSE;
- } else {
- throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
- }
- } else {
- return s;
- }
- }
- }
-
- private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
- return new _ErrorDescriptionBuilder(
- "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
- new _DelayedJQuote(getBooleanFormat()),
- (getBooleanFormat().equals(C_TRUE_FALSE)
- ? ", which is the legacy deprecated default, and we treat it as if no format was set. "
- + "This is the default configuration; you should provide the format explicitly for each "
- + "place where you print a boolean."
- : ".")
- ).tips(
- "Write something like myBool?string('yes', 'no') to specify boolean formatting in place.",
- new Object[]{
- "If you want \"true\"/\"false\" result as you are generating computer-language output "
- + "(not for direct human consumption), then use \"?c\", like ${myBool?c}. (If you "
- + "always generate computer-language output, then it's might be reasonable to set "
- + "the \"", BOOLEAN_FORMAT_KEY, "\" setting to \"c\" instead.)",
- },
- new Object[] {
- "If you need the same two values on most places, the programmers can set the \"",
- BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\". However, then it will be easy to "
- + "unwillingly format booleans like that."
- }
- );
- }
-
- /**
- * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
- * coercion to string is not allowed. The default value is {@code null}.
- *
- * <p>This value is deduced from the {@code "boolean_format"} setting.
- * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
- * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
- * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
- * ignored.
- *
- * @since 2.3.20
- */
- String getTrueStringValue() {
- // The first step deliberately tests booleanFormat instead of trueStringValue!
- return booleanFormat != null ? trueStringValue : (parent != null ? parent.getTrueStringValue() : null);
- }
-
- /**
- * Same as {@link #getTrueStringValue()} but with {@code false}.
- * @since 2.3.20
- */
- String getFalseStringValue() {
- // The first step deliberately tests booleanFormat instead of falseStringValue!
- return booleanFormat != null ? falseStringValue : (parent != null ? parent.getFalseStringValue() : null);
- }
/**
* Sets the format used to convert {@link java.util.Date}-s that are time (no date part) values to string-s, also
@@ -2095,7 +2094,7 @@
throw new IllegalArgumentException("List items must be String-s.");
}
addAutoInclude((String) templateName, this instanceof Configuration && ((Configuration) this)
- .getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_25);
+ .getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_25);
}
}
}
@@ -2176,7 +2175,7 @@
* See {@link #setLocale(Locale)}.
* <br>String value: local codes with the usual format in Java, such as {@code "en_US"}, or since 2.3.26,
* "JVM default" (ignoring case) to use the default locale of the Java environment.
- *
+ *
* <li><p>{@code "classic_compatible"}:
* See {@link #setClassicCompatible(boolean)} and {@link Configurable#setClassicCompatibleAsInt(int)}.
* <br>String value: {@code "true"}, {@code "false"}, also since 2.3.20 {@code 0} or {@code 1} or {@code 2}.
@@ -2192,7 +2191,16 @@
* <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>.
* <br>Example: <code>{ "trade": com.example.TradeTemplateDateFormatFactory,
* "log": com.example.LogTemplateDateFormatFactory }</code>
- *
+ *
+ * <li><p>{@code "c_format"}:
+ * See {@link Configuration#setCFormat(CFormat)}.
+ * <br>String value: {@code "default"} (case insensitive) for the default (on {@link Configuration} only), or
+ * one of the predefined values {@code "JavaScript or JSON"}, {@code "JSON"},
+ * {@code "JavaScript"}, {@code "Java"}, {@code "XS"},
+ * {@code "legacy"}, or
+ * {@code "default"} (only allowed for {@link Configuration} instances) for the default value,
+ * or an <a href="#fm_obe">object builder expression</a> that gives a {@link CFormat} object.
+ *
* <li><p>{@code "template_exception_handler"}:
* See {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
* <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
@@ -2247,7 +2255,7 @@
*
* <li><p>{@code "time_zone"}:
* See {@link #setTimeZone(TimeZone)}.
- * <br>String value: With the format as {@link TimeZone#getTimeZone} defines it. Also, since 2.3.21
+ * <br>String value: With the format as {@link TimeZone#getTimeZone(String)} defines it. Also, since 2.3.21
* {@code "JVM default"} can be used that will be replaced with the actual JVM default time zone when
* {@link #setSetting(String, String)} is called.
* For example {@code "GMT-8:00"} or {@code "America/Los_Angeles"}
@@ -2257,8 +2265,8 @@
* <li><p>{@code sql_date_and_time_time_zone}:
* See {@link #setSQLDateAndTimeTimeZone(TimeZone)}.
* Since 2.3.21.
- * <br>String value: With the format as {@link TimeZone#getTimeZone} defines it. Also, {@code "JVM default"}
- * can be used that will be replaced with the actual JVM default time zone when
+ * <br>String value: With the format as {@link TimeZone#getTimeZone(String)} defines it. Also,
+ * {@code "JVM default"} can be used that will be replaced with the actual JVM default time zone when
* {@link #setSetting(String, String)} is called. Also {@code "null"} can be used, which has the same effect
* as {@link #setSQLDateAndTimeTimeZone(TimeZone) setSQLDateAndTimeTimeZone(null)}.
*
@@ -2405,10 +2413,12 @@
*
* <li><p>{@code "output_format"}:
* See {@link Configuration#setOutputFormat(OutputFormat)}.
- * <br>String value: {@code "default"} (case insensitive) for the default, or an
- * <a href="#fm_obe">object builder expression</a> that gives an {@link OutputFormat}, for example
- * {@code HTMLOutputFormat} or {@code XMLOutputFormat}.
- *
+ * <br>String value: {@code "default"} (case insensitive) for the default,
+ * one of {@code undefined}, {@code HTML}, {@code XHTML}, {@code XML}, {@code RTF}, {@code plainText},
+ * {@code CSS}, {@code JavaScript}, {@code JSON},
+ * or an <a href="#fm_obe">object builder expression</a> that gives an {@link OutputFormat}, for example
+ * {@code HTMLOutputFormat}, or {@code com.example.MyOutputFormat()}.
+ *
* <li><p>{@code "registered_custom_output_formats"}:
* See {@link Configuration#setRegisteredCustomOutputFormats(Collection)}.
* <br>String value: an <a href="#fm_obe">object builder expression</a> that gives a {@link List} of
@@ -2748,6 +2758,20 @@
}
} else if (BOOLEAN_FORMAT_KEY_SNAKE_CASE.equals(name) || BOOLEAN_FORMAT_KEY_CAMEL_CASE.equals(name)) {
setBooleanFormat(value);
+ } else if (C_FORMAT_KEY_SNAKE_CASE.equals(name) || C_FORMAT_KEY_CAMEL_CASE.equals(name)) {
+ if (value.equalsIgnoreCase(DEFAULT)) {
+ if (this instanceof Configuration) {
+ ((Configuration) this).unsetCFormat();
+ } else {
+ throw invalidSettingValueException(name, value);
+ }
+ } else {
+ CFormat cFormat = StandardCFormats.STANDARD_C_FORMATS.get(value);
+ setCFormat(
+ cFormat != null ? cFormat
+ : (CFormat) _ObjectBuilderSettingEvaluator.eval(
+ value, CFormat.class, false, _SettingEvaluationEnvironment.getCurrent()));
+ }
} else if (OUTPUT_ENCODING_KEY_SNAKE_CASE.equals(name) || OUTPUT_ENCODING_KEY_CAMEL_CASE.equals(name)) {
setOutputEncoding(value);
} else if (URL_ESCAPING_CHARSET_KEY_SNAKE_CASE.equals(name)
diff --git a/src/main/java/freemarker/core/DynamicKeyName.java b/src/main/java/freemarker/core/DynamicKeyName.java
index ec46af2..f8cef3a 100644
--- a/src/main/java/freemarker/core/DynamicKeyName.java
+++ b/src/main/java/freemarker/core/DynamicKeyName.java
@@ -34,7 +34,9 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
+import freemarker.template._ObjectWrappers;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.Constants;
/**
@@ -291,7 +293,7 @@
srcIdx += step;
}
// List items are already wrapped:
- return new SimpleSequence(resultList, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ return new SimpleSequence(resultList, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
} else if (targetLazySeq != null) {
// As a targetLazySeq can only occur if a new built-in like ?filter or ?map was used somewhere in the target
// expression, in this case we can return lazily generated sequence without breaking backward compatibility.
@@ -386,7 +388,7 @@
resultList.add(targetIter.next());
}
// List items are already wrapped:
- return new SimpleSequence(resultList, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ return new SimpleSequence(resultList, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
}
}
@@ -433,13 +435,13 @@
"Range top index " + highIndex + " (0-based) is outside the sliced sequence of length " +
srcIdx + ".");
}
- return new SimpleSequence(Arrays.asList(resultElements), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ return new SimpleSequence(Arrays.asList(resultElements), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
}
private TemplateModel emptyResult(boolean seq) {
return seq
- ? (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _TemplateAPI.VERSION_INT_2_3_21
- ? new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER)
+ ? (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _VersionInts.V_2_3_21
+ ? new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER)
: Constants.EMPTY_SEQUENCE)
: TemplateScalarModel.EMPTY_STRING;
}
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index b6b5b44..0bd264b 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -26,8 +26,6 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.text.Collator;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -73,7 +71,8 @@
import freemarker.template.TemplateTransformModel;
import freemarker.template.TransformControl;
import freemarker.template.Version;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
+import freemarker.template._VersionInts;
import freemarker.template.utility.DateUtil;
import freemarker.template.utility.DateUtil.DateToISO8601CalendarFactory;
import freemarker.template.utility.NullWriter;
@@ -103,28 +102,6 @@
private static final Logger LOG = Logger.getLogger("freemarker.runtime");
private static final Logger ATTEMPT_LOGGER = Logger.getLogger("freemarker.runtime.attempt");
- // Do not use this object directly; clone it first! DecimalFormat isn't
- // thread-safe.
- /** "c" number format as it was before Incompatible Improvements 2.3.21. */
- private static final DecimalFormat C_NUMBER_FORMAT_ICI_2_3_20 = new DecimalFormat(
- "0.################",
- new DecimalFormatSymbols(Locale.US));
- static {
- C_NUMBER_FORMAT_ICI_2_3_20.setGroupingUsed(false);
- C_NUMBER_FORMAT_ICI_2_3_20.setDecimalSeparatorAlwaysShown(false);
- }
-
- // Do not use this object directly; clone it first! DecimalFormat isn't
- // thread-safe.
- /** "c" number format as it was starting from Incompatible Improvements 2.3.21. */
- private static final DecimalFormat C_NUMBER_FORMAT_ICI_2_3_21 = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_20.clone();
- static {
- DecimalFormatSymbols symbols = C_NUMBER_FORMAT_ICI_2_3_21.getDecimalFormatSymbols();
- symbols.setInfinity("INF");
- symbols.setNaN("NaN");
- C_NUMBER_FORMAT_ICI_2_3_21.setDecimalFormatSymbols(symbols);
- }
-
private final Configuration configuration;
private final boolean incompatibleImprovementsGE2328;
private final TemplateHashModel rootDataModel;
@@ -165,6 +142,17 @@
@Deprecated
private NumberFormat cNumberFormat;
+ private TemplateNumberFormat cTemplateNumberFormat;
+ private TemplateNumberFormat cTemplateNumberFormatWithPre2331IcIBug;
+
+ /**
+ * Should be a boolean "trueAndFalseStringsCached", but with Incompatible Improvements less than 2.3.22 the
+ * effective value of {@code boolean_format} could change because of {@code #import} and {@code #include},
+ * as those changed the parent template. So we need this cache invalidation trick.
+ */
+ private Configurable trueAndFalseStringsCachedForParent;
+ private String cachedTrueString;
+ private String cachedFalseString;
/**
* Used by the "iso_" built-ins to accelerate formatting.
@@ -217,7 +205,7 @@
public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
super(template);
configuration = template.getConfiguration();
- incompatibleImprovementsGE2328 = configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_28;
+ incompatibleImprovementsGE2328 = configuration.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_28;
this.globalNamespace = new Namespace(null);
this.currentNamespace = mainNamespace = new Namespace(template);
this.out = out;
@@ -519,7 +507,7 @@
if (tc != null
&& !(t instanceof FlowControlException
&& getConfiguration().getIncompatibleImprovements().intValue()
- >= _TemplateAPI.VERSION_INT_2_3_27)) {
+ >= _VersionInts.V_2_3_27)) {
tc.onError(t);
} else {
throw t;
@@ -738,7 +726,7 @@
void invokeNodeHandlerFor(TemplateNodeModel node, TemplateSequenceModel namespaces)
throws TemplateException, IOException {
if (nodeNamespaces == null) {
- SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
ss.add(currentNamespace);
nodeNamespaces = ss;
}
@@ -1135,7 +1123,7 @@
private static SimpleSequence initPositionalCatchAllParameter(Macro.Context macroCtx, String catchAllParamName) {
SimpleSequence positionalCatchAllParamValue;
- positionalCatchAllParamValue = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ positionalCatchAllParamValue = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
macroCtx.setLocalVar(catchAllParamName, positionalCatchAllParamValue);
return positionalCatchAllParamValue;
}
@@ -1143,7 +1131,7 @@
private static SimpleHash initNamedCatchAllParameter(Macro.Context macroCtx, String catchAllParamName) {
SimpleHash namedCatchAllParamValue;
namedCatchAllParamValue = new SimpleHash(
- new LinkedHashMap<String, Object>(), _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+ new LinkedHashMap<String, Object>(), _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
macroCtx.setLocalVar(catchAllParamName, namedCatchAllParamValue);
return namedCatchAllParamValue;
}
@@ -1629,6 +1617,8 @@
return format;
}
+ static final String COMPUTER_FORMAT_STRING = "computer";
+
/**
* Returns the {@link TemplateNumberFormat} for the given parameters without using the {@link Environment}-level
* cache. Of course, the {@link TemplateNumberFormatFactory} involved might still uses its own cache.
@@ -1666,6 +1656,9 @@
}
return formatFactory.get(params, locale, this);
+ } else if (formatStringLen >= 1 && formatString.charAt(0) == 'c'
+ && (formatStringLen == 1 || formatString.equals(COMPUTER_FORMAT_STRING))) {
+ return getCTemplateNumberFormatWithPre2331IcIBug();
} else {
return JavaTemplateNumberFormatFactory.INSTANCE.get(formatString, locale, this);
}
@@ -1676,29 +1669,73 @@
* {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is less than 2.3.31,
* this will wrongly give the format that the <tt>c</tt> built-in used before Incompatible Improvements 2.3.21.
* See more at {@link Configuration#Configuration(Version)}.
+ *
+ * @deprecated Use {@link #getCTemplateNumberFormat()} instead. This method can't return the format used when
+ * {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is 2.3.32,
+ * or greater, and instead it will fall back to return the format that was used for 2.3.31. Also, as its described
+ * earlier, this method was inconsistent with {@code ?c} between Incompatible Improvements 2.3.21 and 2.3.30, while
+ * {@link #getCTemplateNumberFormat()} behaves as {@code ?c} for all Incompatible Improvements value.
*/
+ @Deprecated
public NumberFormat getCNumberFormat() {
- // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
if (cNumberFormat == null) {
- if (configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_31) {
- cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_21.clone();
+ CFormat cFormat = getCFormat();
+ if (cFormat == LegacyCFormat.INSTANCE && configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_31) {
+ // Emulate old bug
+ cNumberFormat = ((LegacyCFormat) cFormat).getLegacyNumberFormat(_VersionInts.V_2_3_20);
} else {
- cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_20.clone();
+ cNumberFormat = cFormat.getLegacyNumberFormat(this);
}
}
return cNumberFormat;
}
/**
- * As we have a number format cache that's shared between {@link Configuration}-s, if the interpretation of a format
- * is impacted by Incompatible Improvements, we must change the cache key.
+ * Returns the {@link TemplateNumberFormat} that {@code ?c}/{@code ?cn} uses.
+ * Calling this method for many times is fine, as it internally caches the result object.
+ * Remember that {@link TemplateNumberFormat}-s aren't thread-safe objects, so the resulting object should only
+ * be used in the same thread where this {@link Environment} runs.
+ *
+ * @since 2.3.32
*/
- String transformNumberFormatGlobalCacheKey(String keyPart) {
- if (configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_31
- && JavaTemplateNumberFormatFactory.COMPUTER.equals(keyPart)) {
- return "computer\u00002";
+ public TemplateNumberFormat getCTemplateNumberFormat() {
+ if (cTemplateNumberFormat == null) {
+ cTemplateNumberFormat = getCFormat().getTemplateNumberFormat(this);
}
- return keyPart;
+ return cTemplateNumberFormat;
+ }
+
+ /**
+ * Like {@link #getCTemplateNumberFormat()}, but emulates the same bug as
+ * {@link #getCNumberFormat()} if a legacy default {@link CFormat} is used.
+ */
+ private TemplateNumberFormat getCTemplateNumberFormatWithPre2331IcIBug() {
+ if (cTemplateNumberFormatWithPre2331IcIBug == null) {
+ CFormat cFormat = getCFormat();
+ if (cFormat == LegacyCFormat.INSTANCE && configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_31) {
+ // Emulate old bug
+ cTemplateNumberFormatWithPre2331IcIBug = ((LegacyCFormat) cFormat).getTemplateNumberFormat(_VersionInts.V_2_3_20);
+ } else {
+ cTemplateNumberFormatWithPre2331IcIBug = cFormat.getTemplateNumberFormat(this);
+ }
+ }
+ return cTemplateNumberFormatWithPre2331IcIBug;
+ }
+
+ @Override
+ public void setCFormat(CFormat cFormat) {
+ CFormat prevCFormat = getCFormat();
+ super.setCFormat(cFormat);
+ if (prevCFormat != cFormat) {
+ cTemplateNumberFormat = null;
+ cTemplateNumberFormatWithPre2331IcIBug = null;
+ cNumberFormat = null;
+ if (cachedTemplateNumberFormats != null) {
+ cachedTemplateNumberFormats.remove(C_FORMAT_STRING);
+ cachedTemplateNumberFormats.remove(COMPUTER_FORMAT_STRING);
+ }
+ clearCachedTrueAndFalseString();
+ }
}
@Override
@@ -1740,6 +1777,118 @@
}
}
+ @Override
+ public void setBooleanFormat(String booleanFormat) {
+ super.setBooleanFormat(booleanFormat);
+ clearCachedTrueAndFalseString();
+ }
+
+ String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
+ if (value) {
+ String s = getTrueStringValue();
+ if (s == null) {
+ if (fallbackToTrueFalse) {
+ return MiscUtil.C_TRUE;
+ } else {
+ throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+ }
+ } else {
+ return s;
+ }
+ } else {
+ String s = getFalseStringValue();
+ if (s == null) {
+ if (fallbackToTrueFalse) {
+ return MiscUtil.C_FALSE;
+ } else {
+ throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+ }
+ } else {
+ return s;
+ }
+ }
+ }
+
+ private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
+ return new _ErrorDescriptionBuilder(
+ "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
+ new _DelayedJQuote(getBooleanFormat()),
+ (getBooleanFormat().equals(BOOLEAN_FORMAT_LEGACY_DEFAULT)
+ ? ", which is the legacy deprecated default, and we treat it as if no format was set. "
+ + "This is the default configuration; you should provide the format explicitly for each "
+ + "place where you print a boolean."
+ : ".")
+ ).tips(
+ "Write something like myBool?string('yes', 'no') to specify boolean formatting in place.",
+ new Object[]{
+ "If you want \"true\"/\"false\" result as you are generating computer-language output "
+ + "(not for direct human consumption), then use \"?c\", like ${myBool?c}. (If you "
+ + "always generate computer-language output, then it's might be reasonable to set "
+ + "the \"", BOOLEAN_FORMAT_KEY, "\" setting to \"c\" instead.)",
+ },
+ new Object[] {
+ "If you need the same two values on most places, the programmers can set the \"",
+ BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\". However, then it will be easy to "
+ + "unwillingly format booleans like that."
+ }
+ );
+ }
+
+ /**
+ * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
+ * coercion to string is not allowed.
+ *
+ * <p>This value is deduced from the {@code "boolean_format"} setting.
+ * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
+ * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
+ * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
+ * ignored, and instead we admit that we don't know how to show boolean values.
+ */
+ String getTrueStringValue() {
+ if (trueAndFalseStringsCachedForParent == getParent()) {
+ return cachedTrueString;
+ }
+ cacheTrueAndFalseStrings();
+ return cachedTrueString;
+ }
+
+ /**
+ * Same as {@link #getTrueStringValue()} but with {@code false}.
+ */
+ String getFalseStringValue() {
+ if (trueAndFalseStringsCachedForParent == getParent()) {
+ return cachedFalseString;
+ }
+ cacheTrueAndFalseStrings();
+ return cachedFalseString;
+ }
+
+ private void clearCachedTrueAndFalseString() {
+ trueAndFalseStringsCachedForParent = null;
+ cachedTrueString = null;
+ cachedFalseString = null;
+ }
+
+ private void cacheTrueAndFalseStrings() {
+ String[] parsedBooleanFormat = parseBooleanFormat(getBooleanFormat());
+ if (parsedBooleanFormat != null) {
+ if (parsedBooleanFormat.length == 0) {
+ CFormat cFormat = getCFormat();
+ cachedTrueString = cFormat.getTrueString();
+ cachedFalseString = cFormat.getFalseString();
+ } else {
+ cachedTrueString = parsedBooleanFormat[0];
+ cachedFalseString = parsedBooleanFormat[1];
+ }
+ } else {
+ // This happens for BOOLEAN_FORMAT_LEGACY_DEFAULT deliberately. That's the default for BC, but it's not a
+ // good default for human audience formatting, so we pretend that it wasn't set.
+ cachedTrueString = null;
+ cachedFalseString = null;
+ }
+ trueAndFalseStringsCachedForParent = getParent();
+ }
+
public Configuration getConfiguration() {
return configuration;
}
@@ -2264,7 +2413,7 @@
}
/**
- * Similar to {@link #getLocalVariable(String)}, but might returns {@link TemplateNullModel}. Only used internally,
+ * Similar to {@link #getLocalVariable(String)}, but might return {@link TemplateNullModel}. Only used internally,
* as {@link TemplateNullModel} is internal.
*
* @since 2.3.29
@@ -3288,12 +3437,12 @@
private Template template;
Namespace() {
- super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
this.template = Environment.this.getTemplate();
}
Namespace(Template template) {
- super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
this.template = template;
}
@@ -3497,11 +3646,11 @@
};
private boolean isBeforeIcI2322() {
- return configuration.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22;
+ return configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22;
}
boolean isIcI2324OrLater() {
- return configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_24;
+ return configuration.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_24;
}
/**
diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java
index a358208..1b469c9 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -33,7 +33,7 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
/**
* Internally used static utilities for evaluation expressions.
@@ -481,7 +481,7 @@
throw InvalidReferenceException.getInstance(exp, env);
} else {
throw new InvalidReferenceException(
- "Null/missing value (no more informatoin avilable)",
+ "Null/missing value (no more information available)",
env);
}
}
@@ -602,7 +602,7 @@
}
if (env.getWrapUncheckedExceptions()) {
return true;
- } else if (env.getConfiguration().getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_27) {
+ } else if (env.getConfiguration().getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_27) {
// We have to judge if we dare to wrap this exception, or it's too likely that some applications try to
// catch it around the template processing to do something special. For the same reason, we only wrap very
// frequent exceptions.
diff --git a/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
index 02e0fba..db47c9a 100644
--- a/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
+++ b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
@@ -264,7 +264,7 @@
}
String currencySymbol = null; // Exceptional, as must be applied after "currency code"
- fetchParamters: do {
+ fetchParameters: do {
int namePos = pos;
String name = fetchName();
if (name == null) {
@@ -308,7 +308,7 @@
skipWS();
} else {
if (pos == ln) {
- break fetchParamters;
+ break fetchParameters;
}
if (pos == paramEndPos) {
throw newExpectedSgParseException("parameter separator whitespace or comma");
diff --git a/src/main/java/freemarker/core/GetOptionalTemplateMethod.java b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
index bb4d2f1..24a3581 100644
--- a/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
+++ b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
@@ -37,7 +37,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.TemplateModelUtils;
/**
@@ -144,7 +144,7 @@
"; see cause exception");
}
- SimpleHash result = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleHash result = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
result.put(RESULT_EXISTS, template != null);
// If the template is missing, result.include and such will be missing too, so that a default can be
// conveniently provided like in <@optTemp.include!myDefaultMacro />.
diff --git a/src/main/java/freemarker/core/HashLiteral.java b/src/main/java/freemarker/core/HashLiteral.java
index f358ba3..cc6d2c9 100644
--- a/src/main/java/freemarker/core/HashLiteral.java
+++ b/src/main/java/freemarker/core/HashLiteral.java
@@ -31,7 +31,9 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
+import freemarker.template._ObjectWrappers;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
@SuppressWarnings("deprecation")
final class HashLiteral extends Expression {
@@ -111,7 +113,7 @@
private TemplateCollectionModel keyCollection, valueCollection; // ordered lists of keys and values
SequenceHash(Environment env) throws TemplateException {
- if (_TemplateAPI.getTemplateLanguageVersionAsInt(HashLiteral.this) >= _TemplateAPI.VERSION_INT_2_3_21) {
+ if (_TemplateAPI.getTemplateLanguageVersionAsInt(HashLiteral.this) >= _VersionInts.V_2_3_21) {
map = new LinkedHashMap<>();
for (int i = 0; i < size; i++) {
Expression keyExp = keys.get(i);
@@ -127,8 +129,8 @@
// Legacy hash literal, where repeated keys were kept when doing ?values or ?keys, yet overwritten when
// doing hash[key].
map = new HashMap<>();
- SimpleSequence keyList = new SimpleSequence(size, _TemplateAPI.SAFE_OBJECT_WRAPPER);
- SimpleSequence valueList = new SimpleSequence(size, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence keyList = new SimpleSequence(size, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
+ SimpleSequence valueList = new SimpleSequence(size, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (int i = 0; i < size; i++) {
Expression keyExp = keys.get(i);
Expression valExp = values.get(i);
@@ -156,7 +158,7 @@
if (keyCollection == null) {
// This can only happen when IcI >= 2.3.21, an the map is a LinkedHashMap.
keyCollection = new CollectionAndSequence(
- new SimpleSequence(map.keySet(), _TemplateAPI.SAFE_OBJECT_WRAPPER));
+ new SimpleSequence(map.keySet(), _ObjectWrappers.SAFE_OBJECT_WRAPPER));
}
return keyCollection;
}
@@ -166,7 +168,7 @@
if (valueCollection == null) {
// This can only happen when IcI >= 2.3.21, an the map is a LinkedHashMap.
valueCollection = new CollectionAndSequence(
- new SimpleSequence(map.values(), _TemplateAPI.SAFE_OBJECT_WRAPPER));
+ new SimpleSequence(map.values(), _ObjectWrappers.SAFE_OBJECT_WRAPPER));
}
return valueCollection;
}
diff --git a/src/main/java/freemarker/core/Interpret.java b/src/main/java/freemarker/core/Interpret.java
index 9b4cb41..4e6694e 100644
--- a/src/main/java/freemarker/core/Interpret.java
+++ b/src/main/java/freemarker/core/Interpret.java
@@ -31,7 +31,7 @@
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.TemplateTransformModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
/**
@@ -81,7 +81,7 @@
}
String templateSource = sourceExpr.evalAndCoerceToPlainText(env);
Template parentTemplate = env.getConfiguration().getIncompatibleImprovements().intValue()
- >= _TemplateAPI.VERSION_INT_2_3_26 ? env.getCurrentTemplate() : env.getTemplate();
+ >= _VersionInts.V_2_3_26 ? env.getCurrentTemplate() : env.getTemplate();
final Template interpretedTemplate;
try {
diff --git a/src/main/java/freemarker/core/JSONCFormat.java b/src/main/java/freemarker/core/JSONCFormat.java
new file mode 100644
index 0000000..40b0cc7
--- /dev/null
+++ b/src/main/java/freemarker/core/JSONCFormat.java
@@ -0,0 +1,56 @@
+/*
+ * 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.core;
+
+import freemarker.template.TemplateException;
+import freemarker.template.utility.StringUtil;
+import freemarker.template.utility.StringUtil.JsStringEncCompatibility;
+import freemarker.template.utility.StringUtil.JsStringEncQuotation;
+
+/**
+ * {@value #NAME} {@link CFormat}; to be used when generating JSON (and not JavaScript), except, in most cases
+ * {@link JavaScriptOrJSONCFormat} is recommended over this.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * most methods are not exposed outside FreeMarker. The class itself and some members are exposed as they are needed for
+ * configuring FreeMarker.
+ *
+ * @see JavaScriptCFormat
+ * @see JavaScriptOrJSONCFormat
+ *
+ * @since 2.3.32
+ */
+public final class JSONCFormat extends AbstractJSONLikeFormat {
+ public static final String NAME = "JSON";
+ public static final JSONCFormat INSTANCE = new JSONCFormat();
+
+ private JSONCFormat() {
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.jsStringEnc(s, JsStringEncCompatibility.JSON, JsStringEncQuotation.QUOTATION_MARK);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/src/main/java/freemarker/core/JSONParser.java b/src/main/java/freemarker/core/JSONParser.java
index ddb01c0..61fc4a9 100644
--- a/src/main/java/freemarker/core/JSONParser.java
+++ b/src/main/java/freemarker/core/JSONParser.java
@@ -20,17 +20,13 @@
package freemarker.core;
import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleNumber;
import freemarker.template.SimpleScalar;
import freemarker.template.SimpleSequence;
-import freemarker.template.Template;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateHashModelEx2;
import freemarker.template.TemplateModel;
@@ -38,7 +34,7 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.Constants;
import freemarker.template.utility.NumberUtil;
import freemarker.template.utility.StringUtil;
@@ -297,7 +293,7 @@
if (tryConsumeChar(']')) return Constants.EMPTY_SEQUENCE;
boolean afterComma = false;
- SimpleSequence elements = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence elements = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
do {
skipWS();
elements.add(consumeValue(afterComma ? null : UNCLOSED_ARRAY_MESSAGE, afterComma ? -1 : startP));
@@ -340,7 +336,7 @@
skipWS();
afterComma = true;
} while (consumeChar(',', '}', UNCLOSED_OBJECT_MESSAGE, startP) == ',');
- return new SimpleHash(map, _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+ return new SimpleHash(map, _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
}
private boolean isE(char c) {
diff --git a/src/main/java/freemarker/core/JavaCFormat.java b/src/main/java/freemarker/core/JavaCFormat.java
new file mode 100644
index 0000000..64c6feb
--- /dev/null
+++ b/src/main/java/freemarker/core/JavaCFormat.java
@@ -0,0 +1,89 @@
+/*
+ * 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.core;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+import freemarker.template.TemplateException;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * {@value #NAME} {@link CFormat}.
+ *
+ * @since 2.3.32
+ */
+public final class JavaCFormat extends CFormat {
+ public static final String NAME = "Java";
+ public static final JavaCFormat INSTANCE = new JavaCFormat();
+
+ private static final TemplateNumberFormat TEMPLATE_NUMBER_FORMAT = new CTemplateNumberFormat(
+ "Double.POSITIVE_INFINITY", "Double.NEGATIVE_INFINITY", "Double.NaN",
+ "Float.POSITIVE_INFINITY", "Float.NEGATIVE_INFINITY", "Float.NaN");
+
+ private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+ = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+
+ static {
+ DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
+ symbols.setInfinity("Double.POSITIVE_INFINITY");
+ symbols.setNaN("Double.NaN");
+ LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalFormatSymbols(symbols);
+ }
+
+ private JavaCFormat() {
+ }
+
+ @Override
+ TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+ return TEMPLATE_NUMBER_FORMAT;
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.javaStringEnc(s, true);
+ }
+
+ @Override
+ String getTrueString() {
+ return "true";
+ }
+
+ @Override
+ String getFalseString() {
+ return "false";
+ }
+
+ @Override
+ String getNullString() {
+ return "null";
+ }
+
+ @Override
+ NumberFormat getLegacyNumberFormat(Environment env) {
+ return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/src/main/java/freemarker/core/JavaScriptCFormat.java b/src/main/java/freemarker/core/JavaScriptCFormat.java
new file mode 100644
index 0000000..5dd7cbc
--- /dev/null
+++ b/src/main/java/freemarker/core/JavaScriptCFormat.java
@@ -0,0 +1,56 @@
+/*
+ * 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.core;
+
+import freemarker.template.TemplateException;
+import freemarker.template.utility.StringUtil;
+import freemarker.template.utility.StringUtil.JsStringEncCompatibility;
+import freemarker.template.utility.StringUtil.JsStringEncQuotation;
+
+/**
+ * {@value #NAME} {@link CFormat}, to be used when generating JavaScript (and not JSON), except, in most cases
+ * {@link JavaScriptOrJSONCFormat} is recommended over this.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * most methods are not exposed outside FreeMarker. The class itself and some members are exposed as they are needed for
+ * configuring FreeMarker.
+ *
+ * @see JSONCFormat
+ * @see JavaScriptOrJSONCFormat
+ *
+ * @since 2.3.32
+ */
+public final class JavaScriptCFormat extends AbstractJSONLikeFormat {
+ public static final String NAME = "JavaScript";
+ public static final JavaScriptCFormat INSTANCE = new JavaScriptCFormat();
+
+ private JavaScriptCFormat() {
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.jsStringEnc(s, JsStringEncCompatibility.JAVA_SCRIPT, JsStringEncQuotation.QUOTATION_MARK);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/src/main/java/freemarker/core/JavaScriptOrJSONCFormat.java b/src/main/java/freemarker/core/JavaScriptOrJSONCFormat.java
new file mode 100644
index 0000000..7c80c47
--- /dev/null
+++ b/src/main/java/freemarker/core/JavaScriptOrJSONCFormat.java
@@ -0,0 +1,64 @@
+/*
+ * 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.core;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.Version;
+import freemarker.template.utility.StringUtil;
+import freemarker.template.utility.StringUtil.JsStringEncCompatibility;
+import freemarker.template.utility.StringUtil.JsStringEncQuotation;
+
+/**
+ * {@value #NAME} {@link CFormat}; for generating output that's compatible with both JSON and JavaScript. This format is
+ * therefore resilient against configuration mistakes, where we generate output in one language, but use the
+ * {@link CFormat} for the other. The small price to pay is that we can't utilize some language-specific opportunities
+ * to make the output a bit shorter, but that hardly matters in practice.
+ * This is the default of {@link Configurable#getCFormat()} starting from
+ * {@link Configuration#Configuration(Version) incompatible_improvements}
+ * {@linkplain Configuration#VERSION_2_3_32 2.3.32}.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * most methods are not exposed outside FreeMarker. The class itself and some members are exposed as they are needed for
+ * configuring FreeMarker.
+ *
+ * @see JavaScriptCFormat
+ * @see JSONCFormat
+ *
+ * @since 2.3.32
+ */
+public final class JavaScriptOrJSONCFormat extends AbstractJSONLikeFormat {
+ public static final String NAME = "JavaScript or JSON";
+ public static final JavaScriptOrJSONCFormat INSTANCE = new JavaScriptOrJSONCFormat();
+
+ private JavaScriptOrJSONCFormat() {
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.jsStringEnc(
+ s, JsStringEncCompatibility.JAVA_SCRIPT_OR_JSON, JsStringEncQuotation.QUOTATION_MARK);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
index 59e3288..5280bb3 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
@@ -23,7 +23,7 @@
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
-final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFormat {
+class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFormat {
private final String formatString;
private final NumberFormat javaNumberFormat;
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java b/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
index 8e8a327..f3bbe74 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
@@ -32,8 +32,6 @@
static final JavaTemplateNumberFormatFactory INSTANCE = new JavaTemplateNumberFormatFactory();
- static final String COMPUTER = "computer";
-
private static final Logger LOG = Logger.getLogger("freemarker.runtime");
private static final ConcurrentHashMap<CacheKey, NumberFormat> GLOBAL_FORMAT_CACHE
@@ -47,9 +45,7 @@
@Override
public TemplateNumberFormat get(String params, Locale locale, Environment env)
throws InvalidFormatParametersException {
- CacheKey cacheKey = new CacheKey(
- env != null ? env.transformNumberFormatGlobalCacheKey(params) : params,
- locale);
+ CacheKey cacheKey = new CacheKey(params, locale);
NumberFormat jFormat = GLOBAL_FORMAT_CACHE.get(cacheKey);
if (jFormat == null) {
if ("number".equals(params)) {
@@ -58,8 +54,6 @@
jFormat = NumberFormat.getCurrencyInstance(locale);
} else if ("percent".equals(params)) {
jFormat = NumberFormat.getPercentInstance(locale);
- } else if (COMPUTER.equals(params)) {
- jFormat = env.getCNumberFormat();
} else {
try {
jFormat = ExtendedDecimalFormatParser.parse(params, locale);
diff --git a/src/main/java/freemarker/core/LegacyCFormat.java b/src/main/java/freemarker/core/LegacyCFormat.java
new file mode 100644
index 0000000..1f3d588
--- /dev/null
+++ b/src/main/java/freemarker/core/LegacyCFormat.java
@@ -0,0 +1,173 @@
+/*
+ * 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.core;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.Version;
+import freemarker.template._VersionInts;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * Corresponds to the behavior of {@code ?c} before {@linkplain Configuration#VERSION_2_3_32 2.3.32} (when there
+ * were no {@link CFormat}-s yet). This only exists for strict backward-compatibility, otherwise avoid this, mostly
+ * because its number-to-string conversion sometimes do rounding, and infinity and NaN formatting has some glitches.
+ * This is the default of {@link Configurable#setCFormat(CFormat) c_format} if
+ * {@link Configuration#Configuration(Version) incompatible_improvements} is less than 2.3.32.
+ *
+ * <p>If {@link Configuration#Configuration(Version) incompatible_improvements} is at least
+ * 2.3.21, then infinity is formatted as {@code INF}, and NaN as
+ * {@code NaN}. If it's less, then infinity is formatted to the infinity character (U+221E), and NaN to the
+ * UNICODE replacement character (U+FFFD). But, because of an old bug that we emulate, this only applies to the behavior
+ * of {@code ?c}/{@code ?cn}, and not to the {@code "c"} and {@code "computer"}
+ * {@link Configurable#setNumberFormat(String) number_format}.
+ * The last uses the pre-2.3.21 format before {@link Configuration#Configuration(Version) incompatible_improvements}
+ * 2.3.31.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
+ * The class itself and some members are exposed as they are needed for configuring FreeMarker.
+ *
+ * @see JavaScriptOrJSONCFormat
+ *
+ * @since 2.3.32
+ */
+public final class LegacyCFormat extends CFormat {
+ public static final LegacyCFormat INSTANCE = new LegacyCFormat();
+ public static final String NAME = "legacy";
+
+ /**
+ * "c" number format as it was before Incompatible Improvements 2.3.21.
+ */
+ static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0 = new DecimalFormat(
+ "0.################",
+ new DecimalFormatSymbols(Locale.US));
+ static {
+ LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.setGroupingUsed(false);
+ LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.setDecimalSeparatorAlwaysShown(false);
+ }
+
+ /**
+ * "c" number format as it was starting from Incompatible Improvements 2.3.21.
+ */
+ private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21 = (DecimalFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+ static {
+ DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21.getDecimalFormatSymbols();
+ symbols.setInfinity("INF");
+ symbols.setNaN("NaN");
+ LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21.setDecimalFormatSymbols(symbols);
+ }
+
+ private LegacyCFormat() {
+ }
+
+ @Override
+ final String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.jsStringEnc(
+ s, StringUtil.JsStringEncCompatibility.JAVA_SCRIPT_OR_JSON, StringUtil.JsStringEncQuotation.QUOTATION_MARK);
+ }
+
+ @Override
+ final TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+ return getTemplateNumberFormat(env.getConfiguration().getIncompatibleImprovements().intValue());
+ }
+
+ TemplateNumberFormat getTemplateNumberFormat(int iciVersion) {
+ return new LegacyCTemplateNumberFormat(getLegacyNumberFormat(iciVersion));
+ }
+
+ @Override
+ String getTrueString() {
+ return "true";
+ }
+
+ @Override
+ String getFalseString() {
+ return "false";
+ }
+
+ @Override
+ final String getNullString() {
+ return "null";
+ }
+
+ @Override
+ NumberFormat getLegacyNumberFormat(Environment env) {
+ // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
+ return getLegacyNumberFormat(env.getConfiguration().getIncompatibleImprovements().intValue());
+ }
+
+ NumberFormat getLegacyNumberFormat(int iciVersion) {
+ NumberFormat numberFormatPrototype;
+ if (iciVersion < _VersionInts.V_2_3_21) {
+ numberFormatPrototype = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0;
+ } else {
+ numberFormatPrototype = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21;
+ }
+ return (NumberFormat) numberFormatPrototype.clone();
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ static final class LegacyCTemplateNumberFormat extends JavaTemplateNumberFormat {
+
+ public LegacyCTemplateNumberFormat(NumberFormat numberFormat) {
+ super(numberFormat, Environment.COMPUTER_FORMAT_STRING);
+ }
+
+ @Override
+ public String formatToPlainText(TemplateNumberModel numberModel) throws UnformattableValueException,
+ TemplateModelException {
+ Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
+ return format(number);
+ }
+
+ @Override
+ public boolean isLocaleBound() {
+ return false;
+ }
+
+ @Override
+ String format(Number number) throws UnformattableValueException {
+ if (number instanceof Integer || number instanceof Long) {
+ // Accelerate these fairly common cases
+ return number.toString();
+ }
+ return super.format(number);
+ }
+
+ @Override
+ public String getDescription() {
+ return "LegacyC(" + super.getDescription() + ")";
+ }
+
+ }
+
+}
diff --git a/src/main/java/freemarker/core/ListLiteral.java b/src/main/java/freemarker/core/ListLiteral.java
index 7745db7..d4f6de6 100644
--- a/src/main/java/freemarker/core/ListLiteral.java
+++ b/src/main/java/freemarker/core/ListLiteral.java
@@ -31,7 +31,7 @@
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
final class ListLiteral extends Expression {
@@ -44,7 +44,7 @@
@Override
TemplateModel _eval(Environment env) throws TemplateException {
- SimpleSequence list = new SimpleSequence(items.size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence list = new SimpleSequence(items.size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (Expression exp : items) {
TemplateModel tm = exp.eval(env);
if (env == null || !env.isClassicCompatible()) {
@@ -140,7 +140,7 @@
TemplateSequenceModel evaluateStringsToNamespaces(Environment env) throws TemplateException {
TemplateSequenceModel val = (TemplateSequenceModel) eval(env);
- SimpleSequence result = new SimpleSequence(val.size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence result = new SimpleSequence(val.size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (int i = 0; i < items.size(); i++) {
Object itemExpr = items.get(i);
if (itemExpr instanceof StringLiteral) {
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index 31d621e..878c4b2 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -37,7 +37,7 @@
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.Constants;
/**
@@ -344,7 +344,7 @@
}
SimpleSequence argsSpecVarValue = new SimpleSequence(
- lengthWithCatchAlls, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ lengthWithCatchAlls, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
argsSpecVarValue.add(argsSpecVarDraft[paramIndex]);
}
@@ -379,7 +379,7 @@
SimpleHash argsSpecVarValue = new SimpleHash(
new LinkedHashMap<String, Object>(lengthWithCatchAlls * 4 / 3, 1.0f),
- _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+ _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
argsSpecVarValue.put(paramNames[paramIndex], argsSpecVarDraft[paramIndex]);
}
diff --git a/src/main/java/freemarker/core/MarkupOutputFormat.java b/src/main/java/freemarker/core/MarkupOutputFormat.java
index 352a122..e98961e 100644
--- a/src/main/java/freemarker/core/MarkupOutputFormat.java
+++ b/src/main/java/freemarker/core/MarkupOutputFormat.java
@@ -26,8 +26,8 @@
/**
* Superclass of {@link OutputFormat}-s that represent a "markup" format, which is any format where certain character
- * sequences have special meaning and thus may need escaping. (Escaping is important for FreeMarker, as typically it has
- * to insert non-markup text from the data-model into the output markup. See also:
+ * sequences have special meaning, and thus may need escaping. (Escaping is important for FreeMarker, as typically it
+ * has to insert non-markup text from the data-model into the output markup. See also:
* {@link Configuration#setOutputFormat(OutputFormat)}.)
*
* <p>
@@ -77,7 +77,7 @@
/**
* Equivalent to calling {@link #fromPlainTextByEscaping(String)} and then
- * {@link #output(TemplateMarkupOutputModel, Writer)}, but the implementation may uses a more efficient solution.
+ * {@link #output(TemplateMarkupOutputModel, Writer)}, but the implementation may use a more efficient solution.
*/
public abstract void output(String textToEsc, Writer out) throws IOException, TemplateModelException;
@@ -99,7 +99,7 @@
/**
* Returns the content as markup text; never {@code null}. If this {@link TemplateMarkupOutputModel} was created
- * with {@link #fromMarkup(String)}, it might returns the original markup text literally, but this is not required
+ * with {@link #fromMarkup(String)}, it might return the original markup text literally, but this is not required
* as far as the returned markup means the same. If this {@link TemplateMarkupOutputModel} wasn't created
* with {@link #fromMarkup(String)} and it doesn't yet have the markup, it has to generate the markup now.
*/
@@ -113,7 +113,7 @@
/**
* Should give the same result as {@link #fromPlainTextByEscaping(String)} and then
- * {@link #getMarkupString(TemplateMarkupOutputModel)}, but the implementation may uses a more efficient solution.
+ * {@link #getMarkupString(TemplateMarkupOutputModel)}, but the implementation may use a more efficient solution.
*/
public abstract String escapePlainText(String plainTextContent) throws TemplateModelException;
diff --git a/src/main/java/freemarker/core/PropertySetting.java b/src/main/java/freemarker/core/PropertySetting.java
index 0c2e22a..117a8c0 100644
--- a/src/main/java/freemarker/core/PropertySetting.java
+++ b/src/main/java/freemarker/core/PropertySetting.java
@@ -38,11 +38,14 @@
private final String key;
private final Expression value;
+ private final ValueSafetyChecker valueSafeyChecker;
static final String[] SETTING_NAMES = new String[] {
// Must be sorted alphabetically!
Configurable.BOOLEAN_FORMAT_KEY_CAMEL_CASE,
Configurable.BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+ Configurable.C_FORMAT_KEY_CAMEL_CASE,
+ Configurable.C_FORMAT_KEY_SNAKE_CASE,
Configurable.CLASSIC_COMPATIBLE_KEY_CAMEL_CASE,
Configurable.CLASSIC_COMPATIBLE_KEY_SNAKE_CASE,
Configurable.DATE_FORMAT_KEY_CAMEL_CASE,
@@ -66,7 +69,7 @@
PropertySetting(Token keyTk, FMParserTokenManager tokenManager, Expression value, Configuration cfg)
throws ParseException {
- String key = keyTk.image;
+ final String key = keyTk.image;
if (Arrays.binarySearch(SETTING_NAMES, key) < 0) {
StringBuilder sb = new StringBuilder();
if (_TemplateAPI.getConfigurationSettingNames(cfg, true).contains(key)
@@ -107,6 +110,26 @@
this.key = key;
this.value = value;
+
+ if (key.equals(Configurable.C_FORMAT_KEY_SNAKE_CASE) || key.equals(Configurable.C_FORMAT_KEY_CAMEL_CASE)) {
+ valueSafeyChecker = new ValueSafetyChecker() {
+ @Override
+ public void check(Environment env, String actualValue) throws TemplateException {
+ if (actualValue.startsWith("@")
+ || StandardCFormats.STANDARD_C_FORMATS.containsKey(actualValue)
+ || actualValue.equals("default")) {
+ return;
+ }
+ throw new TemplateException("It's not allowed to set \"" + key + "\" to "
+ + StringUtil.jQuote(actualValue) + " in a template. Use a standard c format name ("
+ + String.join(", ", StandardCFormats.STANDARD_C_FORMATS.keySet()) + "), " +
+ "or registered custom c format name after a \"@\".",
+ env);
+ }
+ };
+ } else {
+ valueSafeyChecker = null;
+ }
}
@Override
@@ -122,6 +145,9 @@
} else {
strval = value.evalAndCoerceToStringOrUnsupportedMarkup(env);
}
+ if (valueSafeyChecker != null) {
+ valueSafeyChecker.check(env, strval);
+ }
env.setSetting(key, strval);
return null;
}
@@ -171,5 +197,9 @@
boolean isNestedBlockRepeater() {
return false;
}
+
+ private interface ValueSafetyChecker {
+ void check(Environment env, String value) throws TemplateException;
+ }
}
diff --git a/src/main/java/freemarker/core/Range.java b/src/main/java/freemarker/core/Range.java
index a141c7c..bd5620c 100644
--- a/src/main/java/freemarker/core/Range.java
+++ b/src/main/java/freemarker/core/Range.java
@@ -22,6 +22,7 @@
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
/**
* A class that represents a Range between two integers.
@@ -56,7 +57,7 @@
begin, endType != END_SIZE_LIMITED ? lhoValue : begin + lhoValue,
endType == END_INCLUSIVE, endType == END_SIZE_LIMITED);
} else {
- return _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _TemplateAPI.VERSION_INT_2_3_21
+ return _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _VersionInts.V_2_3_21
? new ListableRightUnboundedRangeModel(begin)
: new NonListableRightUnboundedRangeModel(begin);
}
diff --git a/src/main/java/freemarker/core/RecurseNode.java b/src/main/java/freemarker/core/RecurseNode.java
index c7a4b09..e2d326e 100644
--- a/src/main/java/freemarker/core/RecurseNode.java
+++ b/src/main/java/freemarker/core/RecurseNode.java
@@ -28,7 +28,7 @@
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
@@ -58,7 +58,7 @@
}
if (nss != null) {
if (nss instanceof TemplateHashModel) {
- SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
ss.add(nss);
nss = ss;
} else if (!(nss instanceof TemplateSequenceModel)) {
diff --git a/src/main/java/freemarker/core/StandardCFormats.java b/src/main/java/freemarker/core/StandardCFormats.java
new file mode 100644
index 0000000..aa56ccb
--- /dev/null
+++ b/src/main/java/freemarker/core/StandardCFormats.java
@@ -0,0 +1,46 @@
+/*
+ * 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.core;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+final class StandardCFormats {
+ private StandardCFormats() {
+ }
+
+ static final Map<String, CFormat> STANDARD_C_FORMATS;
+ static {
+ Map<String, CFormat> map = new LinkedHashMap<>();
+ addStandardCFormat(map, JavaScriptOrJSONCFormat.INSTANCE);
+ addStandardCFormat(map, JSONCFormat.INSTANCE);
+ addStandardCFormat(map, JavaScriptCFormat.INSTANCE);
+ addStandardCFormat(map, JavaCFormat.INSTANCE);
+ addStandardCFormat(map, XSCFormat.INSTANCE);
+ addStandardCFormat(map, LegacyCFormat.INSTANCE);
+ STANDARD_C_FORMATS = Collections.unmodifiableMap(map);
+ }
+
+ private static void addStandardCFormat(Map<String, CFormat> map, CFormat cFormat) {
+ map.put(cFormat.getName(), cFormat);
+ }
+
+}
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 92cc671..121c18b 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -29,6 +29,7 @@
import freemarker.template.Template;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.NullArgumentException;
/**
@@ -115,7 +116,7 @@
return;
}
- if (((Configuration) cfg).getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22
+ if (((Configuration) cfg).getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22
&& hasAnyConfigurableSet()) {
throw new IllegalStateException(
"This TemplateConfiguration can't be associated to a Configuration that has "
@@ -190,6 +191,9 @@
if (tc.isDateTimeFormatSet()) {
setDateTimeFormat(tc.getDateTimeFormat());
}
+ if (tc.isCFormatSet()) {
+ setCFormat(tc.getCFormat());
+ }
if (tc.isEncodingSet()) {
setEncoding(tc.getEncoding());
}
@@ -332,6 +336,9 @@
if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
template.setDateTimeFormat(getDateTimeFormat());
}
+ if (isCFormatSet() && !template.isCFormatSet()) {
+ template.setCFormat(getCFormat());
+ }
if (isEncodingSet() && template.getEncoding() == null) {
template.setEncoding(getEncoding());
}
@@ -540,7 +547,7 @@
public boolean isOutputFormatSet() {
return outputFormat != null;
}
-
+
/**
* See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}.
*/
@@ -679,6 +686,7 @@
|| isCustomNumberFormatsSet()
|| isDateFormatSet()
|| isDateTimeFormatSet()
+ || isCFormatSet()
|| isLazyImportsSet()
|| isLazyAutoImportsSet()
|| isLocaleSet()
diff --git a/src/main/java/freemarker/core/TemplateMarkupOutputModel.java b/src/main/java/freemarker/core/TemplateMarkupOutputModel.java
index 7043fba..19a02ad 100644
--- a/src/main/java/freemarker/core/TemplateMarkupOutputModel.java
+++ b/src/main/java/freemarker/core/TemplateMarkupOutputModel.java
@@ -27,8 +27,8 @@
* mechanism. Values of this kind are exempt from {@link OutputFormat}-based automatic escaping.
*
* <p>
- * Each implementation of this type has a {@link OutputFormat} subclass pair, whose singleton instance is returned by
- * {@link #getOutputFormat()}. See more about how markup output values work at {@link OutputFormat}.
+ * Each implementation of this type has a corresponding {@link OutputFormat} subclass, whose singleton instance is
+ * returned by {@link #getOutputFormat()}. See more about how markup output values work at {@link OutputFormat}.
*
* <p>
* Note that {@link TemplateMarkupOutputModel}-s are by design not treated like {@link TemplateScalarModel}-s, and so
diff --git a/src/main/java/freemarker/core/TemplateNumberFormat.java b/src/main/java/freemarker/core/TemplateNumberFormat.java
index 2332575..10ca968 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -83,8 +83,7 @@
* will be support, it will be similar to {@link TemplateDateFormat#parse(String, int)}.
*/
public final Object parse(String s) throws TemplateValueFormatException {
- throw new ParsingNotSupportedException("Number formats currenly don't support parsing");
+ throw new ParsingNotSupportedException("Number formats currently don't support parsing");
}
-
-
+
}
diff --git a/src/main/java/freemarker/core/VisitNode.java b/src/main/java/freemarker/core/VisitNode.java
index daaf0a7..8d3006c 100644
--- a/src/main/java/freemarker/core/VisitNode.java
+++ b/src/main/java/freemarker/core/VisitNode.java
@@ -27,7 +27,7 @@
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
@@ -57,7 +57,7 @@
}
if (nss != null) {
if (nss instanceof Environment.Namespace) {
- SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
ss.add(nss);
nss = ss;
} else if (!(nss instanceof TemplateSequenceModel)) {
diff --git a/src/main/java/freemarker/core/XSCFormat.java b/src/main/java/freemarker/core/XSCFormat.java
new file mode 100644
index 0000000..49c4165
--- /dev/null
+++ b/src/main/java/freemarker/core/XSCFormat.java
@@ -0,0 +1,99 @@
+/*
+ * 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.core;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+import freemarker.template.TemplateException;
+
+/**
+ * {@value #NAME} {@link CFormat}, for outputting XML that follows the conventions of XML Schema.
+ *
+ * <p>Note that with this, strings formatted with {@code ?c}/{@code ?cn} aren't quoted, or escaped with backslash, since
+ * those are meaningless in XML, as far as XML Schema is concerned. They are just printed without change. (Note that
+ * XML-escaping is the duty of the auto-escaping facility of FreeMarker, and not of the {@link CFormat}, so that's not
+ * done here either.)
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * most methods are not exposed outside FreeMarker. The class itself and some members are exposed as they are needed for
+ * configuring FreeMarker.
+ *
+ * @since 2.3.32
+ */
+public final class XSCFormat extends CFormat {
+ public static final String NAME = "XS";
+ public static final XSCFormat INSTANCE = new XSCFormat();
+
+ private static final TemplateNumberFormat TEMPLATE_NUMBER_FORMAT = new CTemplateNumberFormat(
+ "INF", "-INF", "NaN",
+ "INF", "-INF", "NaN");
+
+ private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+ = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+ static {
+ DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
+ symbols.setInfinity("INF");
+ symbols.setNaN("NaN");
+ LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalFormatSymbols(symbols);
+ }
+
+ @Override
+ NumberFormat getLegacyNumberFormat(Environment env) {
+ return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+ }
+
+ private XSCFormat() {
+ }
+
+ @Override
+ TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+ return TEMPLATE_NUMBER_FORMAT;
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return s; // So we don't escape here, as we assume that there's XML auto-escaping
+ }
+
+ @Override
+ String getTrueString() {
+ return "true";
+ }
+
+ @Override
+ String getFalseString() {
+ return "false";
+ }
+
+ @Override
+ String getNullString() {
+ // XSD has no null literal, and you have to leave out the whole element to represent null. We can achieve that
+ // here. The closet we can do is causing an empty element or attribute to be outputted. Some frameworks will
+ // interpret that as null if the XSD type is not string.
+ return "";
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/src/main/java/freemarker/core/_ArrayEnumeration.java b/src/main/java/freemarker/core/_ArrayEnumeration.java
index 70d7983..72212a9 100644
--- a/src/main/java/freemarker/core/_ArrayEnumeration.java
+++ b/src/main/java/freemarker/core/_ArrayEnumeration.java
@@ -22,7 +22,7 @@
import java.util.Enumeration;
import java.util.NoSuchElementException;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _ArrayEnumeration implements Enumeration {
private final Object[] array;
diff --git a/src/main/java/freemarker/core/_ArrayIterator.java b/src/main/java/freemarker/core/_ArrayIterator.java
index f7cca25..465ab21 100644
--- a/src/main/java/freemarker/core/_ArrayIterator.java
+++ b/src/main/java/freemarker/core/_ArrayIterator.java
@@ -22,7 +22,7 @@
import java.util.Iterator;
import java.util.NoSuchElementException;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _ArrayIterator implements Iterator {
private final Object[] array;
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index 55a2d3d..df522a1 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -40,9 +40,11 @@
* 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.
- */
+ */
public class _CoreAPI {
-
+ // ATTENTION! Don't refer to other classes in the static initializer of this class! Fields that need that must be
+ // moved into separate class, to avoid class init deadlocks.
+
public static final String ERROR_MESSAGE_HR = "----";
// Can't be instantiated
diff --git a/src/main/java/freemarker/core/_DelayedAOrAn.java b/src/main/java/freemarker/core/_DelayedAOrAn.java
index 8d7306e..2ed0352 100644
--- a/src/main/java/freemarker/core/_DelayedAOrAn.java
+++ b/src/main/java/freemarker/core/_DelayedAOrAn.java
@@ -19,7 +19,7 @@
package freemarker.core;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedAOrAn extends _DelayedConversionToString {
public _DelayedAOrAn(Object object) {
diff --git a/src/main/java/freemarker/core/_DelayedConversionToString.java b/src/main/java/freemarker/core/_DelayedConversionToString.java
index a705c01..65c9fae 100644
--- a/src/main/java/freemarker/core/_DelayedConversionToString.java
+++ b/src/main/java/freemarker/core/_DelayedConversionToString.java
@@ -19,7 +19,7 @@
package freemarker.core;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public abstract class _DelayedConversionToString {
private static final String NOT_SET = new String();
diff --git a/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java b/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
index 93d8713..90f2807 100644
--- a/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
+++ b/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
@@ -22,7 +22,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.utility.ClassUtil;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedFTLTypeDescription extends _DelayedConversionToString {
public _DelayedFTLTypeDescription(TemplateModel tm) {
diff --git a/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java b/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
index 2f070f5..2637766 100644
--- a/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
+++ b/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
@@ -20,7 +20,7 @@
package freemarker.core;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedGetCanonicalForm extends _DelayedConversionToString {
public _DelayedGetCanonicalForm(TemplateObject obj) {
diff --git a/src/main/java/freemarker/core/_DelayedGetMessage.java b/src/main/java/freemarker/core/_DelayedGetMessage.java
index ed2fe56..3254396 100644
--- a/src/main/java/freemarker/core/_DelayedGetMessage.java
+++ b/src/main/java/freemarker/core/_DelayedGetMessage.java
@@ -19,7 +19,7 @@
package freemarker.core;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedGetMessage extends _DelayedConversionToString {
public _DelayedGetMessage(Throwable exception) {
diff --git a/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java b/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
index 56111d0..e3e415a 100644
--- a/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
+++ b/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
@@ -21,7 +21,7 @@
import freemarker.template.TemplateException;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedGetMessageWithoutStackTop extends _DelayedConversionToString {
public _DelayedGetMessageWithoutStackTop(TemplateException exception) {
diff --git a/src/main/java/freemarker/core/_DelayedJQuote.java b/src/main/java/freemarker/core/_DelayedJQuote.java
index 7dfef58..48bfaed 100644
--- a/src/main/java/freemarker/core/_DelayedJQuote.java
+++ b/src/main/java/freemarker/core/_DelayedJQuote.java
@@ -21,7 +21,7 @@
import freemarker.template.utility.StringUtil;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedJQuote extends _DelayedConversionToString {
public _DelayedJQuote(Object object) {
diff --git a/src/main/java/freemarker/core/_DelayedJoinWithComma.java b/src/main/java/freemarker/core/_DelayedJoinWithComma.java
index 68ce8b9..17a64a2 100644
--- a/src/main/java/freemarker/core/_DelayedJoinWithComma.java
+++ b/src/main/java/freemarker/core/_DelayedJoinWithComma.java
@@ -19,7 +19,7 @@
package freemarker.core;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _DelayedJoinWithComma extends _DelayedConversionToString {
public _DelayedJoinWithComma(String[] items) {
diff --git a/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java b/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
index 8c7563d..2b570d8 100644
--- a/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
@@ -31,7 +31,7 @@
import freemarker.template.utility.StringUtil;
/**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
* Packs a structured from of the error description from which the error message can be rendered on-demand.
* Note that this class isn't serializable, thus the containing exception should render the message before it's
* serialized.
diff --git a/src/main/java/freemarker/core/_Java8.java b/src/main/java/freemarker/core/_Java8.java
index 34979da..e1a3954 100644
--- a/src/main/java/freemarker/core/_Java8.java
+++ b/src/main/java/freemarker/core/_Java8.java
@@ -21,7 +21,7 @@
import java.lang.reflect.Method;
/**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
* Used for accessing functionality that's only present in Java 8 or later.
*/
public interface _Java8 {
diff --git a/src/main/java/freemarker/core/_Java8Impl.java b/src/main/java/freemarker/core/_Java8Impl.java
index a25e0f3..e4d14c0 100644
--- a/src/main/java/freemarker/core/_Java8Impl.java
+++ b/src/main/java/freemarker/core/_Java8Impl.java
@@ -21,7 +21,7 @@
import java.lang.reflect.Method;
/**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
* Used for accessing functionality that's only present in Java 8 or later.
*/
// Compile this against Java 8
diff --git a/src/main/java/freemarker/core/_JavaVersions.java b/src/main/java/freemarker/core/_JavaVersions.java
index 3f07673..5670c36 100644
--- a/src/main/java/freemarker/core/_JavaVersions.java
+++ b/src/main/java/freemarker/core/_JavaVersions.java
@@ -23,7 +23,7 @@
import freemarker.template.utility.SecurityUtilities;
/**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
*/
public final class _JavaVersions {
diff --git a/src/main/java/freemarker/core/_MessageUtil.java b/src/main/java/freemarker/core/_MessageUtil.java
index 081835c..69ce60c 100644
--- a/src/main/java/freemarker/core/_MessageUtil.java
+++ b/src/main/java/freemarker/core/_MessageUtil.java
@@ -30,7 +30,7 @@
import freemarker.template.utility.StringUtil;
/**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
* Utilities for creating error messages (and other messages).
*/
public class _MessageUtil {
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
index e262721..cc62b0a 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
@@ -22,7 +22,7 @@
import freemarker.template.utility.StringUtil;
/**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
* Thrown by {@link _ObjectBuilderSettingEvaluator}.
*/
public class _ObjectBuilderSettingEvaluationException extends Exception {
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
index 7e4e18b..0e89e10 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -59,7 +59,7 @@
import freemarker.template.utility.WriteProtectable;
/**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
*
* Evaluates object builder expressions used in configuration {@link Properties}.
* It should be replaced with FTL later (when it was improved to be practical for this), so the syntax should be
@@ -448,7 +448,7 @@
}
} else {
if (resultCoerced) {
- // The FTL way (BigDecimal is loseless, and it will be coerced to the target type later):
+ // The FTL way (BigDecimal is lossless, and it will be coerced to the target type later):
return new BigDecimal(numStr);
} else {
// The Java way (lossy but familiar):
diff --git a/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java b/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
index 88a4abf..f570021 100644
--- a/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
+++ b/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
@@ -25,7 +25,7 @@
import freemarker.template.Configuration;
/**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
* The runtime environment used during the evaluation of configuration {@link Properties}.
*/
public class _SettingEvaluationEnvironment {
diff --git a/src/main/java/freemarker/core/_SortedArraySet.java b/src/main/java/freemarker/core/_SortedArraySet.java
index 4016eb8..3cd2fe3 100644
--- a/src/main/java/freemarker/core/_SortedArraySet.java
+++ b/src/main/java/freemarker/core/_SortedArraySet.java
@@ -23,7 +23,7 @@
import java.util.Collection;
import java.util.Iterator;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _SortedArraySet<E> extends _UnmodifiableSet<E> {
private final E[] array;
diff --git a/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java b/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
index 18e8f07..b370a7b 100644
--- a/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
+++ b/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
@@ -22,7 +22,7 @@
import freemarker.template.TemplateModel;
/**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
*
* <p>Implemented by {@link TemplateModel}-s that can explain why they don't implement a certain type.
* */
diff --git a/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java b/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
index e3b82ab..8d31129 100644
--- a/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
+++ b/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
@@ -22,7 +22,7 @@
import java.util.Iterator;
import java.util.Set;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public class _UnmodifiableCompositeSet<E> extends _UnmodifiableSet<E> {
private final Set<E> set1, set2;
diff --git a/src/main/java/freemarker/core/_UnmodifiableSet.java b/src/main/java/freemarker/core/_UnmodifiableSet.java
index 8c98482..2e5ce69 100644
--- a/src/main/java/freemarker/core/_UnmodifiableSet.java
+++ b/src/main/java/freemarker/core/_UnmodifiableSet.java
@@ -21,7 +21,7 @@
import java.util.AbstractSet;
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
public abstract class _UnmodifiableSet<E> extends AbstractSet<E> {
@Override
diff --git a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
index 58c5cb7..46e9955 100644
--- a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
+++ b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
@@ -49,7 +49,7 @@
import freemarker.template.Template;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateNodeModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.SecurityUtilities;
@@ -602,7 +602,7 @@
}
private static TemplateModel wrapMap(Map table) {
- SimpleHash model = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleHash model = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (Iterator it = table.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
model.put(String.valueOf(entry.getKey()), new SimpleScalar(String.valueOf(entry.getValue())));
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index c321558..47eb5c7 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -67,6 +67,7 @@
import freemarker.template.TemplateSequenceModel;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.RichObjectWrapper;
import freemarker.template.utility.WriteProtectable;
@@ -151,7 +152,7 @@
/**
* {@link String} class name to {@link StaticModel} cache.
* This object only belongs to a single {@link BeansWrapper}.
- * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+ * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good
* reference.
*/
private final StaticModels staticModels;
@@ -159,7 +160,7 @@
/**
* {@link String} class name to {@link EnumerationModel} cache.
* This object only belongs to a single {@link BeansWrapper}.
- * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+ * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good
* reference.
*/
private final ClassBasedModelFactory enumModels;
@@ -259,7 +260,7 @@
* </ul>
*
* <p>Note that the version will be normalized to the lowest version where the same incompatible
- * {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns
+ * {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might return
* a lower version than what you have specified.
*
* @since 2.3.21
@@ -869,7 +870,7 @@
}
static boolean is2321Bugfixed(Version version) {
- return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_21;
+ return version.intValue() >= _VersionInts.V_2_3_21;
}
boolean is2324Bugfixed() {
@@ -877,7 +878,7 @@
}
static boolean is2324Bugfixed(Version version) {
- return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_24;
+ return version.intValue() >= _VersionInts.V_2_3_24;
}
/**
@@ -886,8 +887,8 @@
*/
protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
- return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_27 ? Configuration.VERSION_2_3_27
- : incompatibleImprovements.intValue() == _TemplateAPI.VERSION_INT_2_3_26 ? Configuration.VERSION_2_3_26
+ return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_27 ? Configuration.VERSION_2_3_27
+ : incompatibleImprovements.intValue() == _VersionInts.V_2_3_26 ? Configuration.VERSION_2_3_26
: is2324Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_24
: is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21
: Configuration.VERSION_2_3_0;
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index 7be7ee4..5daaa90 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -24,6 +24,7 @@
import freemarker.template.TemplateDateModel;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
/**
* Holds {@link BeansWrapper} configuration settings and defines their defaults.
@@ -62,7 +63,7 @@
* See the corresponding parameter of {@link BeansWrapper#BeansWrapper(Version)}. Not {@code null}. Note
* that the version will be normalized to the lowest version where the same incompatible
* {@link BeansWrapper} improvements were already present, so for the returned instance
- * {@link #getIncompatibleImprovements()} might returns a lower version than what you have specified
+ * {@link #getIncompatibleImprovements()} might return a lower version than what you have specified
* here.
* @param isIncompImprsAlreadyNormalized
* Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value.
@@ -89,7 +90,7 @@
: BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
this.incompatibleImprovements = incompatibleImprovements;
- preferIndexedReadMethod = incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_27;
+ preferIndexedReadMethod = incompatibleImprovements.intValue() < _VersionInts.V_2_3_27;
classIntrospectorBuilder = new ClassIntrospectorBuilder(incompatibleImprovements);
}
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
index cd4df0f..76e4231 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
@@ -29,6 +29,7 @@
import freemarker.template.Configuration;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.NullArgumentException;
final class ClassIntrospectorBuilder implements Cloneable {
@@ -69,15 +70,15 @@
// to some version changes that affects BeansWrapper, but not the other way around.
this.incompatibleImprovements = normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
treatDefaultMethodsAsBeanMembers
- = incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_26;
+ = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_26;
memberAccessPolicy = DefaultMemberAccessPolicy.getInstance(this.incompatibleImprovements);
}
private static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
// All breakpoints here must occur in BeansWrapper.normalizeIncompatibleImprovements!
- return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_30 ? Configuration.VERSION_2_3_30
- : incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_21 ? Configuration.VERSION_2_3_21
+ return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_30 ? Configuration.VERSION_2_3_30
+ : incompatibleImprovements.intValue() >= _VersionInts.V_2_3_21 ? Configuration.VERSION_2_3_21
: Configuration.VERSION_2_3_0;
}
diff --git a/src/main/java/freemarker/ext/jdom/NodeListModel.java b/src/main/java/freemarker/ext/jdom/NodeListModel.java
index 7e963f7..dc66924 100644
--- a/src/main/java/freemarker/ext/jdom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/jdom/NodeListModel.java
@@ -60,7 +60,7 @@
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* Provides a template for wrapping JDOM objects. It is capable of storing not only
@@ -1168,7 +1168,7 @@
throws Exception {
org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder();
Document document = builder.build(System.in);
- SimpleHash model = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleHash model = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
model.put("document", new NodeListModel(document));
FileReader fr = new FileReader(args[0]);
Template template = new Template(args[0], fr);
diff --git a/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java b/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
index 724ba83..041f892 100644
--- a/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
+++ b/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
@@ -61,7 +61,7 @@
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.UndeclaredThrowableException;
/**
@@ -195,7 +195,7 @@
case PAGE_SCOPE: {
try {
final TemplateModel tm = environment.getGlobalNamespace().get(name);
- if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_22 && unwrapper != null) {
+ if (incompatibleImprovements >= _VersionInts.V_2_3_22 && unwrapper != null) {
return unwrapper.unwrap(tm);
} else { // Legacy behavior branch
if (tm instanceof AdapterTemplateModel) {
@@ -213,7 +213,7 @@
if (tm instanceof TemplateBooleanModel) {
return Boolean.valueOf(((TemplateBooleanModel) tm).getAsBoolean());
}
- if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_22
+ if (incompatibleImprovements >= _VersionInts.V_2_3_22
&& tm instanceof TemplateDateModel) {
return ((TemplateDateModel) tm).getAsDate();
}
diff --git a/src/main/java/freemarker/ext/jsp/JspTagModelBase.java b/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
index 2c4d57b..0fc96a4 100644
--- a/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
+++ b/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
@@ -119,7 +119,7 @@
"). See cause exception for the more specific cause...");
if (e instanceof IllegalArgumentException && !(setterType.isAssignableFrom(String.class))
&& argArray[0] != null && argArray[0] instanceof String) {
- desc.tip("This problem is often caused by unnecessary parameter quotation. Paramters "
+ desc.tip("This problem is often caused by unnecessary parameter quotation. Parameters "
+ "aren't quoted in FTL, similarly as they aren't quoted in most languages. "
+ "For example, these parameter assignments are wrong: ",
"<@my.tag p1=\"true\" p2=\"10\" p3=\"${someVariable}\" p4=\"${x+1}\" />",
diff --git a/src/main/java/freemarker/ext/servlet/InitParamParser.java b/src/main/java/freemarker/ext/servlet/InitParamParser.java
index 8791058..0236e7e 100644
--- a/src/main/java/freemarker/ext/servlet/InitParamParser.java
+++ b/src/main/java/freemarker/ext/servlet/InitParamParser.java
@@ -36,7 +36,7 @@
import freemarker.core._SettingEvaluationEnvironment;
import freemarker.log.Logger;
import freemarker.template.Configuration;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.StringUtil;
@@ -82,7 +82,7 @@
String filePath = pureTemplatePath.substring(TEMPLATE_PATH_PREFIX_FILE.length());
templateLoader = new FileTemplateLoader(new File(filePath));
} else if (pureTemplatePath.startsWith("[")
- && cfg.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22) {
+ && cfg.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22) {
if (!pureTemplatePath.endsWith("]")) {
// B.C. constraint: Can't throw any checked exceptions.
throw new TemplatePathParsingException("Failed to parse template path; closing \"]\" is missing.");
@@ -96,7 +96,7 @@
}
templateLoader = new MultiTemplateLoader(templateLoaders);
} else if (pureTemplatePath.startsWith("{")
- && cfg.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22) {
+ && cfg.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22) {
throw new TemplatePathParsingException("Template paths starting with \"{\" are reseved for future purposes");
} else {
templateLoader = new WebappTemplateLoader(srvCtx, pureTemplatePath);
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index ed47cba..7d4933c 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -59,13 +59,16 @@
import freemarker.cache.TemplateNameFormat;
import freemarker.cache.URLTemplateLoader;
import freemarker.core.BugException;
+import freemarker.core.CFormat;
import freemarker.core.CSSOutputFormat;
import freemarker.core.CombinedMarkupOutputFormat;
import freemarker.core.Configurable;
import freemarker.core.Environment;
import freemarker.core.HTMLOutputFormat;
import freemarker.core.JSONOutputFormat;
+import freemarker.core.JavaScriptOrJSONCFormat;
import freemarker.core.JavaScriptOutputFormat;
+import freemarker.core.LegacyCFormat;
import freemarker.core.MarkupOutputFormat;
import freemarker.core.OutputFormat;
import freemarker.core.ParseException;
@@ -78,6 +81,7 @@
import freemarker.core.UnregisteredOutputFormatException;
import freemarker.core.XHTMLOutputFormat;
import freemarker.core.XMLOutputFormat;
+import freemarker.core.XSCFormat;
import freemarker.core._CoreAPI;
import freemarker.core._DelayedJQuote;
import freemarker.core._MiscTemplateException;
@@ -384,7 +388,7 @@
STANDARD_OUTPUT_FORMATS.put(JavaScriptOutputFormat.INSTANCE.getName(), JavaScriptOutputFormat.INSTANCE);
STANDARD_OUTPUT_FORMATS.put(JSONOutputFormat.INSTANCE.getName(), JSONOutputFormat.INSTANCE);
}
-
+
/**
* The parser decides between {@link #ANGLE_BRACKET_TAG_SYNTAX} and {@link #SQUARE_BRACKET_TAG_SYNTAX} based on the
* first tag (like {@code [#if x]} or {@code <#if x>}) it mets. Note that {@code [=...]} is <em>not</em> a tag, but
@@ -475,7 +479,7 @@
/** FreeMarker version 2.3.31 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_31 = new Version(2, 3, 31);
- /** FreeMarker version 2.3.31 (an {@link #Configuration(Version) incompatible improvements break-point}) */
+ /** FreeMarker version 2.3.32 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_32 = new Version(2, 3, 32);
/** The default of {@link #getIncompatibleImprovements()}, currently {@link #VERSION_2_3_0}. */
@@ -572,6 +576,7 @@
private boolean localeExplicitlySet;
private boolean defaultEncodingExplicitlySet;
private boolean timeZoneExplicitlySet;
+ private boolean cFormatExplicitlySet;
private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap();
@@ -739,7 +744,7 @@
* It won't be affected by {@code #include} and {@code #nested} anymore. This is unintended, a bug with
* {@code incompatible_improvement} 2.3.22 (a consequence of the lower level fixing described in the next
* point). The old behavior of {@code .template_name} is restored if you set
- * {@code incompatible_improvement} to 2.3.23 (while {@link Configurable#getParent()}) of
+ * {@code incompatible_improvement} to 2.3.23 (while {@link Configurable#getParent()} of
* {@link Environment} keeps the changed behavior shown in the next point).
* </li>
* <li><p>
@@ -945,6 +950,32 @@
* U+221E, and U+FFFD.
* </ul>
* </li>
+ * <li><p>
+ * 2.3.32 (or higher):
+ * <ul>
+ * <li><p>
+ * The number formatting of {@code ?c}, {@code ?cn} (and thus also of the {@code "c"}, and
+ * {@code "computer"} {@link Configurable#setNumberFormat(String) number_format}) changes, if the
+ * {@link #setCFormat(CFormat) c_format} setting was left on its default. The default of
+ * {@link #setCFormat(CFormat) c_format} changes to {@link JavaScriptOrJSONCFormat#INSTANCE}, from
+ * {@link LegacyCFormat#INSTANCE}, and that's what contains the changes:</p>
+ * <ul>
+ * <li><p>Changes affecting non-whole numbers, and whole numbers with over 100 digits:
+ * Formatting is now lossless, so it potentially shows much more decimals.
+ * It now uses exponential format (like 1.2E-7 instead of 0.00000012) for numbers whose absolute value
+ * is less than 1E-6 (0.000001), and for whole numbers whose absolute value is at least 1E101 (so over
+ * 100 digits). It also uses exponential format for whole floating point
+ * ({@code double}/{@code Double}}, or {@code float}/{@code Float}) numbers, when their absolute value
+ * is too big for the floating point type to store them precisely (so if the intent was to store some
+ * ID-s, they are likely corrupted anyway, as the type skips some whole numbers).</p></li>
+ * <li><p>Changes affecting floating point infinity: Output changes from {@code INF} to {@code Infinity},
+ * which is the JavaScript and JSON syntax. If you generate XML with XSD-style number syntax (which uses
+ * {@code INF}), but you want the other number formatting changes (recommended), then set
+ * {@link #setCFormat(CFormat) c_format} to {@link XSCFormat#INSTANCE}/{@code "XS"}.</p></li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </li>
* </ul>
*
* @throws IllegalArgumentException
@@ -1017,7 +1048,7 @@
private static TemplateLoader createDefaultTemplateLoader(
Version incompatibleImprovements, TemplateLoader existingTemplateLoader) {
- if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+ if (incompatibleImprovements.intValue() < _VersionInts.V_2_3_21) {
if (existingTemplateLoader instanceof LegacyDefaultFileTemplateLoader) {
return existingTemplateLoader;
}
@@ -1810,7 +1841,7 @@
static TimeZone getDefaultTimeZone() {
return TimeZone.getDefault();
}
-
+
@Override
public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
super.setTemplateExceptionHandler(templateExceptionHandler);
@@ -2005,7 +2036,12 @@
logTemplateExceptionsExplicitlySet = true;
unsetLogTemplateExceptions();
}
-
+
+ if (!cFormatExplicitlySet) {
+ cFormatExplicitlySet = true;
+ unsetCFormat();
+ }
+
if (!wrapUncheckedExceptionsExplicitlySet) {
wrapUncheckedExceptionsExplicitlySet = true;
unsetWrapUncheckedExceptions();
@@ -2449,10 +2485,45 @@
@Override
public boolean getRecognizeStandardFileExtensions() {
return recognizeStandardFileExtensions == null
- ? incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_24
+ ? incompatibleImprovements.intValue() >= _VersionInts.V_2_3_24
: recognizeStandardFileExtensions.booleanValue();
}
+ @Override
+ public void setCFormat(CFormat cFormat) {
+ super.setCFormat(cFormat);
+ cFormatExplicitlySet = true;
+ }
+
+ /**
+ * Resets the setting to its default, as if it was never set. This means that when you change the
+ * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also
+ * {@link #isCFormatExplicitlySet()} will return {@code false}.
+ *
+ * @since 2.3.32
+ */
+ public void unsetCFormat() {
+ if (cFormatExplicitlySet) {
+ setCFormat(getDefaultCFormat(incompatibleImprovements));
+ cFormatExplicitlySet = false;
+ }
+ }
+
+ static CFormat getDefaultCFormat(Version incompatibleImprovements) {
+ return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_32
+ ? JavaScriptOrJSONCFormat.INSTANCE
+ : LegacyCFormat.INSTANCE;
+ }
+
+ /**
+ * Tells if {@link #setCFormat(CFormat)} (or equivalent) was already called on this instance.
+ *
+ * @since 2.3.32
+ */
+ public boolean isCFormatExplicitlySet() {
+ return cFormatExplicitlySet;
+ }
+
/**
* Determines the tag syntax (like {@code <#if x>} VS {@code [#if x]}) of the template files
* that has no {@code #ftl} header to decide that. Don't confuse this with the interpolation syntax
@@ -3522,7 +3593,7 @@
@Override
protected String getCorrectedNameForUnknownSetting(String name) {
if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) {
- // [2.4] Default might changes to camel-case
+ // [2.4] Default might change to camel-case
return DEFAULT_ENCODING_KEY;
}
if ("defaultCharset".equals(name)) {
@@ -3653,7 +3724,7 @@
* @since 2.3.21
*/
public static ObjectWrapper getDefaultObjectWrapper(Version incompatibleImprovements) {
- if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+ if (incompatibleImprovements.intValue() < _VersionInts.V_2_3_21) {
return ObjectWrapper.DEFAULT_WRAPPER;
} else {
return new DefaultObjectWrapperBuilder(incompatibleImprovements).build();
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index eb44ce6..07b1622 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -133,7 +133,7 @@
: new DefaultObjectWrapperConfiguration(bwCfg.getIncompatibleImprovements()) { };
useAdaptersForContainers = dowDowCfg.getUseAdaptersForContainers();
useAdapterForEnumerations = useAdaptersForContainers
- && getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_26;
+ && getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_26;
forceLegacyNonListCollections = dowDowCfg.getForceLegacyNonListCollections();
iterableSupport = dowDowCfg.getIterableSupport();
domNodeSupport = dowDowCfg.getDOMNodeSupport();
@@ -450,8 +450,8 @@
protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
Version bwIcI = BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
- return incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_22
- || bwIcI.intValue() >= _TemplateAPI.VERSION_INT_2_3_22
+ return incompatibleImprovements.intValue() < _VersionInts.V_2_3_22
+ || bwIcI.intValue() >= _VersionInts.V_2_3_22
? bwIcI : Configuration.VERSION_2_3_22;
}
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
index a9575bc..da93fee 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
@@ -43,8 +43,8 @@
_TemplateAPI.checkCurrentVersionNotRecycled(
incompatibleImprovements,
"freemarker.configuration", "DefaultObjectWrapper");
- useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22;
- forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.VERSION_INT_2_4_0;
+ useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22;
+ forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.V_2_4_0;
domNodeSupport = true;
jythonSupport = true;
}
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 578f48d..06e9cb1 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -343,9 +343,9 @@
private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
int v = incompatibleImprovements.intValue();
- if (v < _TemplateAPI.VERSION_INT_2_3_19) {
+ if (v < _VersionInts.V_2_3_19) {
return Configuration.VERSION_2_3_0;
- } else if (v > _TemplateAPI.VERSION_INT_2_3_21) {
+ } else if (v > _VersionInts.V_2_3_21) {
return Configuration.VERSION_2_3_21;
} else { // if 2.3.19 or 2.3.20 or 2.3.21
return incompatibleImprovements;
diff --git a/src/main/java/freemarker/template/_ObjectWrappers.java b/src/main/java/freemarker/template/_ObjectWrappers.java
new file mode 100644
index 0000000..511531b
--- /dev/null
+++ b/src/main/java/freemarker/template/_ObjectWrappers.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ */
+// Be careful to not refer this class in the static initializers of other classes that (indirectly) refer to this class,
+// as that can lead to deadlock as the class initialization locks are acquired by the JVM! This is also why this was
+// extracted from _TemplateAPI.
+public final class _ObjectWrappers {
+ /**
+ * Kind of a dummy {@link ObjectWrapper} used at places where the internal code earlier used the
+ * {@link ObjectWrapper#DEFAULT_WRAPPER} singleton, because it wasn't supposed to wrap/unwrap anything with it;
+ * never use this {@link ObjectWrapper} in situations where values of arbitrary types need to be wrapped!
+ * The typical situation is that we are using {@link SimpleSequence}, or {@link SimpleHash}, which always has an
+ * {@link ObjectWrapper} field, even if we don't care in the given situation, and so we didn't set it explicitly.
+ * The concern with the old way is that the {@link ObjectWrapper} set in the {@link Configuration} is possibly
+ * more restrictive than the default, so if the template author can somehow make FreeMarker wrap something with the
+ * default {@link ObjectWrapper}, then we got a security problem. So we try not to have that around, if possible.
+ * The obvious fix, and the better engineering would be just use a {@link TemplateSequenceModel} or
+ * {@link TemplateHashModelEx2} implementation at those places, which doesn't have an {@link ObjectWrapper} (and
+ * doesn't have the overhead of said implementations either). But, some user code might casts the values it
+ * receives (as directive argument for example) to {@link SimpleSequence} or {@link SimpleHash}, instead of to
+ * {@link TemplateSequenceModel} or {@link TemplateHashModelEx2}. Such user code is wrong, but still, if it worked
+ * so far fine (especially as sequence/hash literals are implemented by these "Simple" classes), it's better if it
+ * keeps working when they upgrade to 2.3.30. Such user code will be still out of luck if it also tries to add items
+ * which are not handled by {@link SimpleObjectWrapper}, but such abuse is even more unlikely, and this is how far
+ * we could go with this backward compatibility hack.
+ *
+ * @since 2.3.30
+ */
+ public static final SimpleObjectWrapper SAFE_OBJECT_WRAPPER;
+ static {
+ SAFE_OBJECT_WRAPPER = new SimpleObjectWrapper(Configuration.VERSION_2_3_0);
+ SAFE_OBJECT_WRAPPER.writeProtect();
+ }
+
+}
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index a43842f..8fa3250 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -27,6 +27,7 @@
import freemarker.cache.TemplateLoader;
import freemarker.cache.TemplateLookupStrategy;
import freemarker.cache.TemplateNameFormat;
+import freemarker.core.CFormat;
import freemarker.core.Expression;
import freemarker.core.OutputFormat;
import freemarker.core.TemplateObject;
@@ -36,55 +37,12 @@
/**
* 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.
- */
+ * access things inside this package that users shouldn't.
+ */
public class _TemplateAPI {
-
- // Constants for faster access... probably unnecessary and should be removed.
- public static final int VERSION_INT_2_3_0 = Configuration.VERSION_2_3_0.intValue();
- public static final int VERSION_INT_2_3_19 = Configuration.VERSION_2_3_19.intValue();
- public static final int VERSION_INT_2_3_20 = Configuration.VERSION_2_3_20.intValue();
- public static final int VERSION_INT_2_3_21 = Configuration.VERSION_2_3_21.intValue();
- public static final int VERSION_INT_2_3_22 = Configuration.VERSION_2_3_22.intValue();
- public static final int VERSION_INT_2_3_23 = Configuration.VERSION_2_3_23.intValue();
- public static final int VERSION_INT_2_3_24 = Configuration.VERSION_2_3_24.intValue();
- public static final int VERSION_INT_2_3_25 = Configuration.VERSION_2_3_25.intValue();
- public static final int VERSION_INT_2_3_26 = Configuration.VERSION_2_3_26.intValue();
- public static final int VERSION_INT_2_3_27 = Configuration.VERSION_2_3_27.intValue();
- public static final int VERSION_INT_2_3_28 = Configuration.VERSION_2_3_28.intValue();
- public static final int VERSION_INT_2_3_29 = Configuration.VERSION_2_3_29.intValue();
- public static final int VERSION_INT_2_3_30 = Configuration.VERSION_2_3_30.intValue();
- public static final int VERSION_INT_2_3_31 = Configuration.VERSION_2_3_31.intValue();
- public static final int VERSION_INT_2_3_32 = Configuration.VERSION_2_3_32.intValue();
- public static final int VERSION_INT_2_4_0 = Version.intValueFor(2, 4, 0);
+ // ATTENTION! Don't refer to other classes in the static initializer of this class! Fields that need that must be
+ // moved into a separate class, to avoid class init deadlocks.
- /**
- * Kind of a dummy {@link ObjectWrapper} used at places where the internal code earlier used the
- * {@link ObjectWrapper#DEFAULT_WRAPPER} singleton, because it wasn't supposed to wrap/unwrap anything with it;
- * never use this {@link ObjectWrapper} in situations where values of arbitrary types need to be wrapped!
- * The typical situation is that we are using {@link SimpleSequence}, or {@link SimpleHash}, which always has an
- * {@link ObjectWrapper} field, even if we don't care in the given situation, and so we didn't set it explicitly.
- * The concern with the old way is that the {@link ObjectWrapper} set in the {@link Configuration} is possibly
- * more restrictive than the default, so if the template author can somehow make FreeMarker wrap something with the
- * default {@link ObjectWrapper}, then we got a security problem. So we try not to have that around, if possible.
- * The obvious fix, and the better engineering would be just use a {@link TemplateSequenceModel} or
- * {@link TemplateHashModelEx2} implementation at those places, which doesn't have an {@link ObjectWrapper} (and
- * doesn't have the overhead of said implementations either). But, some user code might casts the values it
- * receives (as directive argument for example) to {@link SimpleSequence} or {@link SimpleHash}, instead of to
- * {@link TemplateSequenceModel} or {@link TemplateHashModelEx2}. Such user code is wrong, but still, if it worked
- * so far fine (especially as sequence/hash literals are implemented by these "Simple" classes), it's better if it
- * keeps working when they upgrade to 2.3.30. Such user code will be still out of luck if it also tries to add items
- * which are not handled by {@link SimpleObjectWrapper}, but such abuse is even more unlikely, and this is how far
- * we could go with this backward compatibility hack.
- *
- * @since 2.3.30
- */
- public static final SimpleObjectWrapper SAFE_OBJECT_WRAPPER;
- static {
- SAFE_OBJECT_WRAPPER = new SimpleObjectWrapper(Configuration.VERSION_2_3_0);
- SAFE_OBJECT_WRAPPER.writeProtect();
- }
-
public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
int iciV = incompatibleImprovements.intValue();
@@ -93,7 +51,7 @@
+ incompatibleImprovements + ", but the installed FreeMarker version is only "
+ Configuration.getVersion() + ". You may need to upgrade FreeMarker in your project.");
}
- if (iciV < VERSION_INT_2_3_0) {
+ if (iciV < _VersionInts.V_2_3_0) {
throw new IllegalArgumentException("\"incompatibleImprovements\" must be at least 2.3.0.");
}
}
@@ -228,11 +186,15 @@
public static Locale getDefaultLocale() {
return Configuration.getDefaultLocale();
}
-
+
public static TimeZone getDefaultTimeZone() {
return Configuration.getDefaultTimeZone();
}
+ public static CFormat getDefaultCFormat(Version incompatibleImprovements) {
+ return Configuration.getDefaultCFormat(incompatibleImprovements);
+ }
+
public static void setPreventStrippings(Configuration conf, boolean preventStrippings) {
conf.setPreventStrippings(preventStrippings);
}
diff --git a/src/main/java/freemarker/template/_VersionInts.java b/src/main/java/freemarker/template/_VersionInts.java
new file mode 100644
index 0000000..22ba09d
--- /dev/null
+++ b/src/main/java/freemarker/template/_VersionInts.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ */
+// Because we refer to other classes in the static initializer of this class, be careful with referring this class in
+// the static initializers of other classes, as that can lead to deadlock if the class initialization locks are acquired
+// by the JVM in different orders! This is also why this was extracted from _TemplateAPI.
+public final class _VersionInts {
+ private _VersionInts() {
+ }
+
+ // Constants for faster access... probably unnecessary and should be removed.
+ public static final int V_2_3_0 = Configuration.VERSION_2_3_0.intValue();
+ public static final int V_2_3_19 = Configuration.VERSION_2_3_19.intValue();
+ public static final int V_2_3_20 = Configuration.VERSION_2_3_20.intValue();
+ public static final int V_2_3_21 = Configuration.VERSION_2_3_21.intValue();
+ public static final int V_2_3_22 = Configuration.VERSION_2_3_22.intValue();
+ public static final int V_2_3_23 = Configuration.VERSION_2_3_23.intValue();
+ public static final int V_2_3_24 = Configuration.VERSION_2_3_24.intValue();
+ public static final int V_2_3_25 = Configuration.VERSION_2_3_25.intValue();
+ public static final int V_2_3_26 = Configuration.VERSION_2_3_26.intValue();
+ public static final int V_2_3_27 = Configuration.VERSION_2_3_27.intValue();
+ public static final int V_2_3_28 = Configuration.VERSION_2_3_28.intValue();
+ public static final int V_2_3_30 = Configuration.VERSION_2_3_30.intValue();
+ public static final int V_2_3_31 = Configuration.VERSION_2_3_31.intValue();
+ public static final int V_2_3_32 = Configuration.VERSION_2_3_32.intValue();
+ public static final int V_2_4_0 = Version.intValueFor(2, 4, 0);
+}
diff --git a/src/main/java/freemarker/template/utility/CollectionUtils.java b/src/main/java/freemarker/template/utility/CollectionUtils.java
index 814fd1f..a164286 100644
--- a/src/main/java/freemarker/template/utility/CollectionUtils.java
+++ b/src/main/java/freemarker/template/utility/CollectionUtils.java
@@ -26,6 +26,8 @@
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[] { };
+ public static final String[] EMPTY_STRING_ARRAY = new String[] { };
+
public static final Class[] EMPTY_CLASS_ARRAY = new Class[] { };
/**
diff --git a/src/main/java/freemarker/template/utility/DOMNodeModel.java b/src/main/java/freemarker/template/utility/DOMNodeModel.java
index b1c6f1b..8a1ffef 100644
--- a/src/main/java/freemarker/template/utility/DOMNodeModel.java
+++ b/src/main/java/freemarker/template/utility/DOMNodeModel.java
@@ -38,7 +38,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* A convenient wrapper class for wrapping a Node in the W3C DOM API.
@@ -74,7 +74,7 @@
if ("attributes".equals(key)) {
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
- SimpleHash hash = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleHash hash = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (int i = 0; i < attributes.getLength(); i++) {
Attr att = (Attr) attributes.item(i);
hash.put(att.getName(), att.getValue());
diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java
index 3317955..6d5f3fa 100644
--- a/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/src/main/java/freemarker/template/utility/StringUtil.java
@@ -19,6 +19,8 @@
package freemarker.template.utility;
+import static freemarker.template.utility.StringUtil.JsStringEncQuotation.*;
+
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
@@ -881,39 +883,7 @@
if (s == null) {
return "null";
}
- int ln = s.length();
- StringBuilder b = new StringBuilder(ln + 4);
- b.append('"');
- for (int i = 0; i < ln; i++) {
- char c = s.charAt(i);
- if (c == '"') {
- b.append("\\\"");
- } else if (c == '\\') {
- b.append("\\\\");
- } else if (c < 0x20) {
- if (c == '\n') {
- b.append("\\n");
- } else if (c == '\r') {
- b.append("\\r");
- } else if (c == '\f') {
- b.append("\\f");
- } else if (c == '\b') {
- b.append("\\b");
- } else if (c == '\t') {
- b.append("\\t");
- } else {
- b.append("\\u00");
- int x = c / 0x10;
- b.append(toHexDigit(x));
- x = c & 0xF;
- b.append(toHexDigit(x));
- }
- } else {
- b.append(c);
- }
- } // for each characters
- b.append('"');
- return b.toString();
+ return javaStringEnc(s, true);
}
/**
@@ -934,7 +904,7 @@
return "null";
}
int ln = s.length();
- StringBuilder b = new StringBuilder(ln + 4);
+ StringBuilder b = new StringBuilder(ln + 6);
b.append('"');
for (int i = 0; i < ln; i++) {
char c = s.charAt(i);
@@ -957,15 +927,13 @@
b.append("\\t");
} else {
b.append("\\u00");
- int x = c / 0x10;
- b.append(toHexDigit(x));
- x = c & 0xF;
- b.append(toHexDigit(x));
+ b.append(toHexDigitLowerCase(c / 0x10));
+ b.append(toHexDigitLowerCase(c & 0xF));
}
} else {
b.append(c);
}
- } // for each characters
+ } // for each character
b.append('"');
return b.toString();
}
@@ -1281,25 +1249,39 @@
public static boolean isBackslashEscapedFTLIdentifierCharacter(final char c) {
return c == '-' || c == '.' || c == ':' || c == '#';
}
-
+
/**
- * Escapes the <code>String</code> with the escaping rules of Java language
+ * Escapes the {@code String} with the escaping rules of Java language
* string literals, so it's safe to insert the value into a string literal.
* The resulting string will not be quoted.
- *
+ *
+ * See more details at {@link #javaStringEnc(String, boolean)}, as this just calls that with {@code false} as the
+ * 2nd argument.
+ */
+ public static String javaStringEnc(String s) {
+ return javaStringEnc(s, false);
+ }
+
+ /**
+ * Escapes the {@code String} with the escaping rules of Java language string literals, and then if {@code quote} is
+ * true, it also adds quotation marks before and after it.
+ *
* <p>All characters under UCS code point 0x20 will be escaped.
* Where they have no dedicated escape sequence in Java, they will
* be replaced with hexadecimal escape (<tt>\</tt><tt>u<i>XXXX</i></tt>).
*
* @see #jQuote(String)
*/
- public static String javaStringEnc(String s) {
+ public static String javaStringEnc(String s, boolean quote) {
int ln = s.length();
for (int i = 0; i < ln; i++) {
char c = s.charAt(i);
if (c == '"' || c == '\\' || c < 0x20) {
- StringBuilder b = new StringBuilder(ln + 4);
- b.append(s.substring(0, i));
+ StringBuilder b = new StringBuilder(ln + (quote ? 6 : 4));
+ if (quote) {
+ b.append("\"");
+ }
+ b.append(s, 0, i);
while (true) {
if (c == '"') {
b.append("\\\"");
@@ -1318,41 +1300,42 @@
b.append("\\t");
} else {
b.append("\\u00");
- int x = c / 0x10;
- b.append((char)
- (x < 0xA ? x + '0' : x - 0xA + 'a'));
- x = c & 0xF;
- b.append((char)
- (x < 0xA ? x + '0' : x - 0xA + 'a'));
+ b.append(toHexDigitLowerCase(c / 0x10));
+ b.append(toHexDigitLowerCase(c & 0xF));
}
} else {
b.append(c);
}
i++;
if (i >= ln) {
+ if (quote) {
+ b.append("\"");
+ }
return b.toString();
}
c = s.charAt(i);
}
} // if has to be escaped
- } // for each characters
- return s;
+ } // for each character
+ return quote ? '"' + s + '"' : s;
}
/**
* Escapes a {@link String} to be safely insertable into a JavaScript string literal; for more see
- * {@link #jsStringEnc(String, boolean) jsStringEnc(s, false)}.
+ * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+ * jsStringEnc(s, JsStringEncCompatibility.JAVA_SCRIPT, null)}.
*/
public static String javaScriptStringEnc(String s) {
- return jsStringEnc(s, false);
+ return jsStringEnc(s, JsStringEncCompatibility.JAVA_SCRIPT);
}
/**
* Escapes a {@link String} to be safely insertable into a JSON string literal; for more see
- * {@link #jsStringEnc(String, boolean) jsStringEnc(s, true)}.
+ * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+ * jsStringEnc(s, JsStringEncCompatibility.JSON, null)}.
*/
public static String jsonStringEnc(String s) {
- return jsStringEnc(s, true);
+ return jsStringEnc(s, JsStringEncCompatibility.JSON);
}
private static final int NO_ESC = 0;
@@ -1360,20 +1343,43 @@
private static final int ESC_BACKSLASH = 3;
/**
- * Escapes a {@link String} to be safely insertable into a JavaScript or a JSON string literal.
- * The resulting string will <em>not</em> be quoted; the caller must ensure that they are there in the final
- * output. Note that for JSON, the quotation marks must be {@code "}, not {@code '}, because JSON doesn't escape
- * {@code '}.
- *
+ * Escapes a {@link String} to be safely insertable into a JSON or JavaScript string literal; for more see
+ * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+ * jsStringEnc(s, json ? JsStringEncCompatibility.JSON : JsStringEncCompatibility.JAVA_SCRIPT, null)}.
+ *
+ * @since 2.3.20
+ * @deprecated Use {@link #jsStringEnc(String, JsStringEncCompatibility)} instead.
+ */
+ @Deprecated
+ public static String jsStringEnc(String s, boolean json) {
+ return jsStringEnc(s, json ? JsStringEncCompatibility.JSON : JsStringEncCompatibility.JAVA_SCRIPT, null);
+ }
+
+ /**
+ * Escapes a {@link String} to be safely insertable into a JSON or JavaScript string literal; for more see
+ * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation) jsStringEnc(s, compatibility, null)}.
+ *
+ * @since 2.3.32
+ */
+ public static String jsStringEnc(String s, JsStringEncCompatibility compatibility) {
+ return jsStringEnc(s, compatibility, null);
+ }
+
+ /**
+ * Escapes a {@link String} to be safely insertable into a JavaScript or a JSON string literal, and if the 3rd
+ * argument is {@code true}, also adds quotation marks around it.
+ * If instead the caller ensures that the quotation marks are there, then in JSON mode (2nd argument), the quotation
+ * marks must be {@code "}, not {@code '}, because for JSON we won't escape {@code '}.
+ *
* <p>The escaping rules guarantee that if the inside of the JavaScript/JSON string literal is from one or more
* touching pieces that were escaped with this, no character sequence can occur that closes the
* JavaScript/JSON string literal, or has a meaning in HTML/XML that causes the HTML script section to be closed.
* (If, however, the escaped section is preceded by or followed by strings from other sources, this can't be
* guaranteed in some rare cases. Like <tt>x = "</${a?js_string}"</tt> might closes the "script"
* element if {@code a} is {@code "script>"}.)
- *
+ *
* The escaped characters are:
- *
+ *
* <table style="width: auto; border-collapse: collapse" border="1" summary="Characters escaped by jsStringEnc">
* <tr>
* <th>Input
@@ -1382,7 +1388,7 @@
* <td><tt>"</tt>
* <td><tt>\"</tt>
* <tr>
- * <td><tt>'</tt> if not in JSON-mode
+ * <td><tt>'</tt> if not in JSON-mode, nor is the {@code quited} argument {@code true}
* <td><tt>\'</tt>
* <tr>
* <td><tt>\</tt>
@@ -1394,7 +1400,7 @@
* <td><tt>></tt> if the method can't know that it won't be directly after <tt>]]</tt> or <tt>--</tt>
* <td>JavaScript: <tt>\></tt>; JSON: <tt>\</tt><tt>u003E</tt>
* <tr>
- * <td><tt><</tt> if the method can't know that it won't be directly followed by <tt>!</tt> or <tt>?</tt>
+ * <td><tt><</tt> if the method can't know that it won't be directly followed by <tt>!</tt> or <tt>?</tt>
* <td><tt><tt>\</tt>u003C</tt>
* <tr>
* <td>
@@ -1408,14 +1414,27 @@
* u2029 (Paragraph separator - source code line-break in ECMAScript)<br>
* <td><tt>\<tt>u</tt><i>XXXX</i></tt>
* </table>
- *
- * @since 2.3.20
+ *
+ * @param s The string to escape
+ * @param compatibility If escaping should restrict itself to rules that are valid in JSON, in JavaScript, or in both.
+ * @param quotation In not {@code null}, quotation marks of this type are added around the value.
+ *
+ * @since 2.3.32
*/
- public static String jsStringEnc(String s, boolean json) {
+ public static String jsStringEnc(String s, JsStringEncCompatibility compatibility, JsStringEncQuotation quotation) {
NullArgumentException.check("s", s);
int ln = s.length();
- StringBuilder sb = null;
+ StringBuilder sb;
+ if (quotation == null) {
+ sb = null;
+ } else {
+ if (quotation == APOSTROPHE && compatibility.jsonCompatible) {
+ throw new IllegalArgumentException("JSON compatible mode doesn't allow quotationMode=" + quotation);
+ }
+ sb = new StringBuilder(ln + 8);
+ sb.append(quotation.getSymbol());
+ }
for (int i = 0; i < ln; i++) {
final char c = s.charAt(i);
final int escapeType; //
@@ -1435,16 +1454,22 @@
escapeType = ESC_HEXA;
}
} else if (c == '"') {
- escapeType = ESC_BACKSLASH;
+ escapeType = quotation == APOSTROPHE ? NO_ESC : ESC_BACKSLASH;
} else if (c == '\'') {
- escapeType = json ? NO_ESC : ESC_BACKSLASH;
+ escapeType = !compatibility.javaScriptCompatible || quotation == QUOTATION_MARK ? NO_ESC
+ : (compatibility.jsonCompatible ? ESC_HEXA : ESC_BACKSLASH);
} else if (c == '\\') {
escapeType = ESC_BACKSLASH;
- } else if (c == '/' && (i == 0 || s.charAt(i - 1) == '<')) { // against closing elements
- escapeType = ESC_BACKSLASH;
- } else if (c == '>') { // against "]]> and "-->"
+ } else if (c == '/'
+ && (i == 0 && quotation == null || i != 0 && s.charAt(i - 1) == '<')) {
+ // against closing elements with "</"
+ escapeType = ESC_BACKSLASH;
+ } else if (c == '>') {
+ // against "]]> and "-->"
final boolean dangerous;
- if (i == 0) {
+ if (quotation != null && i < 2) {
+ dangerous = false;
+ } else if (i == 0) {
dangerous = true;
} else {
final char prevC = s.charAt(i - 1);
@@ -1459,11 +1484,12 @@
dangerous = false;
}
}
- escapeType = dangerous ? (json ? ESC_HEXA : ESC_BACKSLASH) : NO_ESC;
- } else if (c == '<') { // against "<!"
+ escapeType = dangerous ? (compatibility.jsonCompatible ? ESC_HEXA : ESC_BACKSLASH) : NO_ESC;
+ } else if (c == '<') {
+ // against "<!"
final boolean dangerous;
if (i == ln - 1) {
- dangerous = true;
+ dangerous = quotation == null;
} else {
char nextC = s.charAt(i + 1);
dangerous = nextC == '!' || nextC == '?';
@@ -1480,24 +1506,24 @@
if (escapeType != NO_ESC) { // If needs escaping
if (sb == null) {
sb = new StringBuilder(ln + 6);
- sb.append(s.substring(0, i));
+ sb.append(s, 0, i);
}
sb.append('\\');
if (escapeType > 0x20) {
sb.append((char) escapeType);
} else if (escapeType == ESC_HEXA) {
- if (!json && c < 0x100) {
+ if (!compatibility.jsonCompatible && c < 0x100) {
sb.append('x');
- sb.append(toHexDigit(c >> 4));
- sb.append(toHexDigit(c & 0xF));
+ sb.append(toHexDigitUpperCase(c >> 4));
+ sb.append(toHexDigitUpperCase(c & 0xF));
} else {
sb.append('u');
int cp = c;
- sb.append(toHexDigit((cp >> 12) & 0xF));
- sb.append(toHexDigit((cp >> 8) & 0xF));
- sb.append(toHexDigit((cp >> 4) & 0xF));
- sb.append(toHexDigit(cp & 0xF));
+ sb.append(toHexDigitUpperCase((cp >> 12) & 0xF));
+ sb.append(toHexDigitUpperCase((cp >> 8) & 0xF));
+ sb.append(toHexDigitUpperCase((cp >> 4) & 0xF));
+ sb.append(toHexDigitUpperCase(cp & 0xF));
}
} else { // escapeType == ESC_BACKSLASH
sb.append(c);
@@ -1509,15 +1535,23 @@
// Needs no escaping
if (sb != null) sb.append(c);
- } // for each characters
+ } // for each character
+
+ if (quotation != null) {
+ sb.append(quotation.getSymbol());
+ }
return sb == null ? s : sb.toString();
}
- private static char toHexDigit(int d) {
+ private static char toHexDigitLowerCase(int d) {
+ return (char) (d < 0xA ? d + '0' : d - 0xA + 'a');
+ }
+
+ private static char toHexDigitUpperCase(int d) {
return (char) (d < 0xA ? d + '0' : d - 0xA + 'A');
}
-
+
/**
* Parses a name-value pair list, where the pairs are separated with comma,
* and the name and value is separated with colon.
@@ -2156,5 +2190,59 @@
}
return sb.toString();
}
-
+
+ /**
+ * Used as the argument of {@link StringUtil#jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)}.
+ *
+ * @since 2.3.32
+ */
+ public enum JsStringEncCompatibility {
+ /**
+ * Output is expected to be used in JavaScript, not in JSON.
+ */
+ JAVA_SCRIPT(true, false),
+
+ /**
+ * Output is expected to be used in JSON, not in JavaScript. While JSON is compatible with JavaScript, in this
+ * mode we don't care about escaping apostrophe, as it's not special in JSON.
+ */
+ JSON(false, true),
+
+ /**
+ * Output is expected to be used both in JSON and JavaScript.
+ */
+ JAVA_SCRIPT_OR_JSON(true, true);
+
+ JsStringEncCompatibility(boolean javaScriptCompatible, boolean jsonCompatible) {
+ this.javaScriptCompatible = javaScriptCompatible;
+ this.jsonCompatible = jsonCompatible;
+ }
+
+ private final boolean javaScriptCompatible;
+ private final boolean jsonCompatible;
+
+ boolean isJSONCompatible() {
+ return jsonCompatible;
+ }
+ }
+
+ /**
+ * Used as the argument of {@link StringUtil#jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)}.
+ *
+ * @since 2.3.32
+ */
+ public enum JsStringEncQuotation {
+ QUOTATION_MARK('"'),
+ APOSTROPHE('\'');
+
+ private final char symbol;
+
+ JsStringEncQuotation(char symbol) {
+ this.symbol = symbol;
+ }
+
+ public char getSymbol() {
+ return symbol;
+ }
+ }
}
diff --git a/src/main/java/freemarker/template/utility/TemplateModelUtils.java b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
index 4efff56..a95f409 100644
--- a/src/main/java/freemarker/template/utility/TemplateModelUtils.java
+++ b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
@@ -39,7 +39,7 @@
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* Static utility method related to {@link TemplateModel}-s that didn't fit elsewhere.
@@ -258,7 +258,7 @@
private void initKeys() throws TemplateModelException {
if (keys == null) {
Set<String> keySet = new HashSet<>();
- SimpleSequence keySeq = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence keySeq = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
for (TemplateHashModelEx hash : hashes) {
addKeys(keySet, keySeq, hash);
}
@@ -281,7 +281,7 @@
private void initValues() throws TemplateModelException {
if (values == null) {
- SimpleSequence seq = new SimpleSequence(size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+ SimpleSequence seq = new SimpleSequence(size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
// Note: size() invokes initKeys() if needed.
int ln = keys.size();
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a6ee15b..1ecfa4d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -292,22 +292,22 @@
this.outputFormat = outputFormat;
recalculateAutoEscapingField();
- if (incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_24) {
+ if (incompatibleImprovements < _VersionInts.V_2_3_24) {
// Emulate bug, where the string literal parser haven't inherited the IcI:
- incompatibleImprovements = _TemplateAPI.VERSION_INT_2_3_0;
+ incompatibleImprovements = _VersionInts.V_2_3_0;
}
-
+
// So that loop variable built-ins, like ?index, works inside the interpolations in the string literal:
iteratorBlockContexts = parentParser.iteratorBlockContexts;
}
void tearDownStringLiteralMode(FMParser parentParser) {
// If the naming convention was established inside the string literal, it's inherited by the parent:
- FMParserTokenManager parentTokenSource = parentParser.token_source;
+ FMParserTokenManager parentTokenSource = parentParser.token_source;
parentTokenSource.namingConvention = token_source.namingConvention;
parentTokenSource.namingConventionEstabilisher = token_source.namingConventionEstabilisher;
}
-
+
/**
* Used when we need to recreate the source code from the AST (such as for the FM2 to FM3 converter).
*/
@@ -350,7 +350,7 @@
}
return null;
}
-
+
/**
* Updates the {@link #autoEscaping} field based on the {@link #autoEscapingPolicy} and {@link #outputFormat} fields.
*/
@@ -369,7 +369,7 @@
autoEscaping = false;
}
}
-
+
MarkupOutputFormat getMarkupOutputFormat() {
return outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null;
}
@@ -382,7 +382,7 @@
? Configuration.SQUARE_BRACKET_TAG_SYNTAX
: Configuration.ANGLE_BRACKET_TAG_SYNTAX;
}
-
+
/**
* Don't use it, unless you are developing FreeMarker itself.
* The naming convention used by this template; if it couldn't be detected so far, it will be the most probable one.
@@ -513,14 +513,14 @@
}
throw new ParseException("Expecting boolean (true/false) parameter", exp);
}
-
+
void checkCurrentOutputFormatCanEscape(Token start) throws ParseException {
if (!(outputFormat instanceof MarkupOutputFormat)) {
throw new ParseException("The current output format can't do escaping: " + outputFormat,
template, start);
}
- }
-
+ }
+
private ParserIteratorBlockContext pushIteratorBlockContext() {
if (iteratorBlockContexts == null) {
iteratorBlockContexts = new ArrayList<ParserIteratorBlockContext>(4);
@@ -529,16 +529,16 @@
iteratorBlockContexts.add(newCtx);
return newCtx;
}
-
+
private void popIteratorBlockContext() {
iteratorBlockContexts.remove(iteratorBlockContexts.size() - 1);
}
-
+
private ParserIteratorBlockContext peekIteratorBlockContext() {
int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
- return size != 0 ? (ParserIteratorBlockContext) iteratorBlockContexts.get(size - 1) : null;
+ return size != 0 ? (ParserIteratorBlockContext) iteratorBlockContexts.get(size - 1) : null;
}
-
+
private void checkLoopVariableBuiltInLHO(String loopVarName, Expression lhoExp, Token biName)
throws ParseException {
int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
@@ -560,8 +560,8 @@
+ "but there's no loop variable in scope with this name: " + loopVarName,
lhoExp);
}
-
- private String forEachDirectiveSymbol() {
+
+ private String forEachDirectiveSymbol() {
// [2.4] Use camel case as the default
return token_source.namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "#forEach" : "#foreach";
}
@@ -685,7 +685,7 @@
// We only get here if this is a strict FTL tag.
tagSyntaxEstablished = true;
- if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+ if (incompatibleImprovements >= _VersionInts.V_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
// For END_xxx tags, as they can't contain expressions, the whole tag is a single token. So this is the only
// chance to check if we got something inconsistent like `</#if]`. (We can't do this at the #CLOSE_TAG1 or
@@ -702,16 +702,16 @@
}
} // if end-tag
}
-
+
checkNamingConvention(tok, tokenNamingConvention);
-
+
SwitchTo(newLexState);
}
void checkNamingConvention(Token tok) {
- checkNamingConvention(tok, _CoreStringUtils.getIdentifierNamingConvention(tok.image));
+ checkNamingConvention(tok, _CoreStringUtils.getIdentifierNamingConvention(tok.image));
}
-
+
void checkNamingConvention(Token tok, int tokenNamingConvention) {
if (tokenNamingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION) {
if (namingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION) {
@@ -722,7 +722,7 @@
}
}
}
-
+
private TokenMgrError newNameConventionMismatchException(Token tok) {
return new TokenMgrError(
"Naming convention mismatch. "
@@ -733,7 +733,7 @@
+ (namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
? "camel case naming convention (like: exampleName) "
: (namingConvention == Configuration.LEGACY_NAMING_CONVENTION
- ? "legacy naming convention (directive (tag) names are like examplename, "
+ ? "legacy naming convention (directive (tag) names are like examplename, "
+ "everything else is like example_name) "
: "??? (internal error)"
))
@@ -755,11 +755,11 @@
private void handleTagSyntaxAndSwitch(Token tok, int newLexState) {
handleTagSyntaxAndSwitch(tok, Configuration.AUTO_DETECT_NAMING_CONVENTION, newLexState);
}
-
+
private boolean isStrictTag(String image) {
return image.length() > 2 && (image.charAt(1) == '#' || image.charAt(2) == '#');
}
-
+
/**
* Detects the naming convention used, both in start- and end-tag tokens.
*
@@ -773,7 +773,7 @@
static char getTagNameCharAt(Token tok, int charIdxInName) {
final String image = tok.image;
-
+
// Skip tag delimiter:
int idx = 0;
for (;;) {
@@ -817,7 +817,7 @@
}
private void startInterpolation(Token tok) {
- if (
+ if (
interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
&& tok.kind == SQUARE_BRACKET_INTERPOLATION_OPENING
|| interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX
@@ -827,7 +827,7 @@
tok.kind = STATIC_TEXT_NON_WS;
return;
}
-
+
if (postInterpolationLexState != -1) {
// This certainly never occurs, as starting an interpolation in expression mode fails earlier.
char c = tok.image.charAt(0);
@@ -847,7 +847,7 @@
SwitchTo(postInterpolationLexState);
postInterpolationLexState = -1;
}
-
+
private TokenMgrError newUnexpectedClosingTokenException(Token closingTk) {
return new TokenMgrError(
"You can't have an \"" + closingTk.image + "\" here, as there's nothing open that it could close.",
@@ -921,7 +921,7 @@
*/
<ATTEMPT : <START_TAG> "attempt" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
- <RECOVER : <START_TAG> "recover" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
+ <RECOVER : <START_TAG> "recover" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
|
<IF : <START_TAG> "if" <BLANK>> { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
|
@@ -1096,7 +1096,7 @@
*/
<UNKNOWN_DIRECTIVE : ("[#" | "[/#" | "<#" | "</#") (["a"-"z", "A"-"Z", "_"])+>
{
- if (!tagSyntaxEstablished && incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_19) {
+ if (!tagSyntaxEstablished && incompatibleImprovements < _VersionInts.V_2_3_19) {
matchedToken.kind = STATIC_TEXT_NON_WS;
} else {
char firstChar = matchedToken.image.charAt(0);
@@ -1121,7 +1121,7 @@
// unknown directive:
if (_CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES.contains(dn)) {
throw new TokenMgrError(
- "#" + dn + " is an existing directive, but the tag is malformed. "
+ "#" + dn + " is an existing directive, but the tag is malformed. "
+ " (See FreeMarker Manual / Directive Reference.)",
TokenMgrError.LEXICAL_ERROR,
matchedToken.beginLine, matchedToken.beginColumn + 1,
@@ -1214,7 +1214,7 @@
("x" ["0"-"9", "A"-"F", "a"-"f"])
)
>
- |
+ |
<STRING_LITERAL :
(
"\""
@@ -1322,12 +1322,12 @@
// There's a legacy glitch where you can always close a tag with `]`, like `<#if x]`. We have to keep that
// working for backward compatibility, hence we don't always throw at !squBracTagSyntax:
if (!squBracTagSyntax
- && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+ && (incompatibleImprovements >= _VersionInts.V_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)
|| postInterpolationLexState != -1 /* We're in an interpolation => We aren't in a tag */) {
throw newUnexpectedClosingTokenException(matchedToken);
}
-
+
// Close tag, either legally or to emulate legacy glitch:
matchedToken.kind = DIRECTIVE_END;
if (inFTLHeader) {
@@ -1379,7 +1379,7 @@
// Remove backslashes from Token.image:
final String s = matchedToken.image;
if (s.indexOf('\\') != -1) {
- final int srcLn = s.length();
+ final int srcLn = s.length();
final char[] newS = new char[srcLn - 1];
int dstIdx = 0;
for (int srcIdx = 0; srcIdx < srcLn; srcIdx++) {
@@ -1411,136 +1411,136 @@
<#NON_ESCAPED_ID_START_CHAR:
[
// This was generated on JDK 1.8.0_20 Win64 with src/main/misc/identifierChars/IdentifierCharGenerator.java
- "$",
- "@" - "Z",
- "_",
- "a" - "z",
- "\u00AA",
- "\u00B5",
- "\u00BA",
- "\u00C0" - "\u00D6",
- "\u00D8" - "\u00F6",
- "\u00F8" - "\u1FFF",
- "\u2071",
- "\u207F",
- "\u2090" - "\u209C",
- "\u2102",
- "\u2107",
- "\u210A" - "\u2113",
- "\u2115",
- "\u2119" - "\u211D",
- "\u2124",
- "\u2126",
- "\u2128",
- "\u212A" - "\u212D",
- "\u212F" - "\u2139",
- "\u213C" - "\u213F",
- "\u2145" - "\u2149",
- "\u214E",
- "\u2183" - "\u2184",
- "\u2C00" - "\u2C2E",
- "\u2C30" - "\u2C5E",
- "\u2C60" - "\u2CE4",
- "\u2CEB" - "\u2CEE",
- "\u2CF2" - "\u2CF3",
- "\u2D00" - "\u2D25",
- "\u2D27",
- "\u2D2D",
- "\u2D30" - "\u2D67",
- "\u2D6F",
- "\u2D80" - "\u2D96",
- "\u2DA0" - "\u2DA6",
- "\u2DA8" - "\u2DAE",
- "\u2DB0" - "\u2DB6",
- "\u2DB8" - "\u2DBE",
- "\u2DC0" - "\u2DC6",
- "\u2DC8" - "\u2DCE",
- "\u2DD0" - "\u2DD6",
- "\u2DD8" - "\u2DDE",
- "\u2E2F",
- "\u3005" - "\u3006",
- "\u3031" - "\u3035",
- "\u303B" - "\u303C",
- "\u3040" - "\u318F",
- "\u31A0" - "\u31BA",
- "\u31F0" - "\u31FF",
- "\u3300" - "\u337F",
- "\u3400" - "\u4DB5",
- "\u4E00" - "\uA48C",
- "\uA4D0" - "\uA4FD",
- "\uA500" - "\uA60C",
- "\uA610" - "\uA62B",
- "\uA640" - "\uA66E",
- "\uA67F" - "\uA697",
- "\uA6A0" - "\uA6E5",
- "\uA717" - "\uA71F",
- "\uA722" - "\uA788",
- "\uA78B" - "\uA78E",
- "\uA790" - "\uA793",
- "\uA7A0" - "\uA7AA",
- "\uA7F8" - "\uA801",
- "\uA803" - "\uA805",
- "\uA807" - "\uA80A",
- "\uA80C" - "\uA822",
- "\uA840" - "\uA873",
- "\uA882" - "\uA8B3",
- "\uA8D0" - "\uA8D9",
- "\uA8F2" - "\uA8F7",
- "\uA8FB",
- "\uA900" - "\uA925",
- "\uA930" - "\uA946",
- "\uA960" - "\uA97C",
- "\uA984" - "\uA9B2",
- "\uA9CF" - "\uA9D9",
- "\uAA00" - "\uAA28",
- "\uAA40" - "\uAA42",
- "\uAA44" - "\uAA4B",
- "\uAA50" - "\uAA59",
- "\uAA60" - "\uAA76",
- "\uAA7A",
- "\uAA80" - "\uAAAF",
- "\uAAB1",
- "\uAAB5" - "\uAAB6",
- "\uAAB9" - "\uAABD",
- "\uAAC0",
- "\uAAC2",
- "\uAADB" - "\uAADD",
- "\uAAE0" - "\uAAEA",
- "\uAAF2" - "\uAAF4",
- "\uAB01" - "\uAB06",
- "\uAB09" - "\uAB0E",
- "\uAB11" - "\uAB16",
- "\uAB20" - "\uAB26",
- "\uAB28" - "\uAB2E",
- "\uABC0" - "\uABE2",
- "\uABF0" - "\uABF9",
- "\uAC00" - "\uD7A3",
- "\uD7B0" - "\uD7C6",
- "\uD7CB" - "\uD7FB",
- "\uF900" - "\uFB06",
- "\uFB13" - "\uFB17",
- "\uFB1D",
- "\uFB1F" - "\uFB28",
- "\uFB2A" - "\uFB36",
- "\uFB38" - "\uFB3C",
- "\uFB3E",
- "\uFB40" - "\uFB41",
- "\uFB43" - "\uFB44",
- "\uFB46" - "\uFBB1",
- "\uFBD3" - "\uFD3D",
- "\uFD50" - "\uFD8F",
- "\uFD92" - "\uFDC7",
- "\uFDF0" - "\uFDFB",
- "\uFE70" - "\uFE74",
- "\uFE76" - "\uFEFC",
- "\uFF10" - "\uFF19",
- "\uFF21" - "\uFF3A",
- "\uFF41" - "\uFF5A",
- "\uFF66" - "\uFFBE",
- "\uFFC2" - "\uFFC7",
- "\uFFCA" - "\uFFCF",
- "\uFFD2" - "\uFFD7",
- "\uFFDA" - "\uFFDC"
+ "$",
+ "@" - "Z",
+ "_",
+ "a" - "z",
+ "\u00AA",
+ "\u00B5",
+ "\u00BA",
+ "\u00C0" - "\u00D6",
+ "\u00D8" - "\u00F6",
+ "\u00F8" - "\u1FFF",
+ "\u2071",
+ "\u207F",
+ "\u2090" - "\u209C",
+ "\u2102",
+ "\u2107",
+ "\u210A" - "\u2113",
+ "\u2115",
+ "\u2119" - "\u211D",
+ "\u2124",
+ "\u2126",
+ "\u2128",
+ "\u212A" - "\u212D",
+ "\u212F" - "\u2139",
+ "\u213C" - "\u213F",
+ "\u2145" - "\u2149",
+ "\u214E",
+ "\u2183" - "\u2184",
+ "\u2C00" - "\u2C2E",
+ "\u2C30" - "\u2C5E",
+ "\u2C60" - "\u2CE4",
+ "\u2CEB" - "\u2CEE",
+ "\u2CF2" - "\u2CF3",
+ "\u2D00" - "\u2D25",
+ "\u2D27",
+ "\u2D2D",
+ "\u2D30" - "\u2D67",
+ "\u2D6F",
+ "\u2D80" - "\u2D96",
+ "\u2DA0" - "\u2DA6",
+ "\u2DA8" - "\u2DAE",
+ "\u2DB0" - "\u2DB6",
+ "\u2DB8" - "\u2DBE",
+ "\u2DC0" - "\u2DC6",
+ "\u2DC8" - "\u2DCE",
+ "\u2DD0" - "\u2DD6",
+ "\u2DD8" - "\u2DDE",
+ "\u2E2F",
+ "\u3005" - "\u3006",
+ "\u3031" - "\u3035",
+ "\u303B" - "\u303C",
+ "\u3040" - "\u318F",
+ "\u31A0" - "\u31BA",
+ "\u31F0" - "\u31FF",
+ "\u3300" - "\u337F",
+ "\u3400" - "\u4DB5",
+ "\u4E00" - "\uA48C",
+ "\uA4D0" - "\uA4FD",
+ "\uA500" - "\uA60C",
+ "\uA610" - "\uA62B",
+ "\uA640" - "\uA66E",
+ "\uA67F" - "\uA697",
+ "\uA6A0" - "\uA6E5",
+ "\uA717" - "\uA71F",
+ "\uA722" - "\uA788",
+ "\uA78B" - "\uA78E",
+ "\uA790" - "\uA793",
+ "\uA7A0" - "\uA7AA",
+ "\uA7F8" - "\uA801",
+ "\uA803" - "\uA805",
+ "\uA807" - "\uA80A",
+ "\uA80C" - "\uA822",
+ "\uA840" - "\uA873",
+ "\uA882" - "\uA8B3",
+ "\uA8D0" - "\uA8D9",
+ "\uA8F2" - "\uA8F7",
+ "\uA8FB",
+ "\uA900" - "\uA925",
+ "\uA930" - "\uA946",
+ "\uA960" - "\uA97C",
+ "\uA984" - "\uA9B2",
+ "\uA9CF" - "\uA9D9",
+ "\uAA00" - "\uAA28",
+ "\uAA40" - "\uAA42",
+ "\uAA44" - "\uAA4B",
+ "\uAA50" - "\uAA59",
+ "\uAA60" - "\uAA76",
+ "\uAA7A",
+ "\uAA80" - "\uAAAF",
+ "\uAAB1",
+ "\uAAB5" - "\uAAB6",
+ "\uAAB9" - "\uAABD",
+ "\uAAC0",
+ "\uAAC2",
+ "\uAADB" - "\uAADD",
+ "\uAAE0" - "\uAAEA",
+ "\uAAF2" - "\uAAF4",
+ "\uAB01" - "\uAB06",
+ "\uAB09" - "\uAB0E",
+ "\uAB11" - "\uAB16",
+ "\uAB20" - "\uAB26",
+ "\uAB28" - "\uAB2E",
+ "\uABC0" - "\uABE2",
+ "\uABF0" - "\uABF9",
+ "\uAC00" - "\uD7A3",
+ "\uD7B0" - "\uD7C6",
+ "\uD7CB" - "\uD7FB",
+ "\uF900" - "\uFB06",
+ "\uFB13" - "\uFB17",
+ "\uFB1D",
+ "\uFB1F" - "\uFB28",
+ "\uFB2A" - "\uFB36",
+ "\uFB38" - "\uFB3C",
+ "\uFB3E",
+ "\uFB40" - "\uFB41",
+ "\uFB43" - "\uFB44",
+ "\uFB46" - "\uFBB1",
+ "\uFBD3" - "\uFD3D",
+ "\uFD50" - "\uFD8F",
+ "\uFD92" - "\uFDC7",
+ "\uFDF0" - "\uFDFB",
+ "\uFE70" - "\uFE74",
+ "\uFE76" - "\uFEFC",
+ "\uFF10" - "\uFF19",
+ "\uFF21" - "\uFF3A",
+ "\uFF41" - "\uFF5A",
+ "\uFF66" - "\uFFBE",
+ "\uFFC2" - "\uFFC7",
+ "\uFFCA" - "\uFFCF",
+ "\uFFD2" - "\uFFD7",
+ "\uFFDA" - "\uFFDC"
]
>
|
@@ -1569,7 +1569,7 @@
|
<EMPTY_DIRECTIVE_END : "/>" | "/]">
{
- if (tagSyntaxEstablished && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+ if (tagSyntaxEstablished && (incompatibleImprovements >= _VersionInts.V_2_3_28
|| interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)) {
String image = matchedToken.image;
char lastChar = image.charAt(image.length() - 1);
@@ -3518,11 +3518,11 @@
// To prevent parser check loopholes like <#list ...><#macro ...><#break></#macro></#list>.
lastIteratorBlockContexts = iteratorBlockContexts;
iteratorBlockContexts = null;
- if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
- lastBreakableDirectiveNesting = breakableDirectiveNesting;
- lastContinuableDirectiveNesting = continuableDirectiveNesting;
- breakableDirectiveNesting = 0;
- continuableDirectiveNesting = 0;
+ if (incompatibleImprovements >= _VersionInts.V_2_3_23) {
+ lastBreakableDirectiveNesting = breakableDirectiveNesting;
+ lastContinuableDirectiveNesting = continuableDirectiveNesting;
+ breakableDirectiveNesting = 0;
+ continuableDirectiveNesting = 0;
} else {
lastBreakableDirectiveNesting = 0; // Just to prevent uninitialized local variable error later
lastContinuableDirectiveNesting = 0;
@@ -3542,11 +3542,11 @@
)
{
iteratorBlockContexts = lastIteratorBlockContexts;
- if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
+ if (incompatibleImprovements >= _VersionInts.V_2_3_23) {
breakableDirectiveNesting = lastBreakableDirectiveNesting;
continuableDirectiveNesting = lastContinuableDirectiveNesting;
}
-
+
inMacro = inFunction = false;
Macro result = new Macro(
name, paramNamesWithDefault, catchAllParamName, isFunction, requireArgsSpecialVariable, children);
@@ -4305,7 +4305,7 @@
{
buf.setLength(buf.length() - t.image.length());
if (!t.image.endsWith(";")
- && _TemplateAPI.getTemplateLanguageVersionAsInt(template) >= _TemplateAPI.VERSION_INT_2_3_21) {
+ && _TemplateAPI.getTemplateLanguageVersionAsInt(template) >= _VersionInts.V_2_3_21) {
throw new ParseException("Unclosed \"" + start.image + "\"", template, start);
}
return t;
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index b1caabc..2ac2604 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20,16 +20,13 @@
<book conformance="docgen" version="5.0" xml:lang="en"
xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:ns5="http://www.w3.org/1998/Math/MathML"
- xmlns:ns4="http://www.w3.org/1999/xhtml"
- xmlns:ns3="http://www.w3.org/2000/svg"
- xmlns:ns="http://docbook.org/ns/docbook">
+>
<info>
<title>Apache FreeMarker Manual</title>
<titleabbrev>Manual</titleabbrev>
- <productname>Freemarker 2.3.31</productname>
+ <productname>Freemarker 2.3.32</productname>
</info>
<preface role="index.html" xml:id="preface">
@@ -420,8 +417,8 @@
and "directive" synonyms.)</para>
<note>
- <para>You can easily try writing templates on <link
- xlink:href="http://freemarker-online.kenshoo.com/">http://freemarker-online.kenshoo.com/</link></para>
+ <para>You can easily try writing templates on <olink
+ targetdoc="onlineTemplateTester"/></para>
</note>
<section>
@@ -816,6 +813,16 @@
</listitem>
</itemizedlist>
</listitem>
+
+ <listitem>
+ <para><literal>product.id?c</literal> formats
+ <literal>product.id</literal> (let's say that's a number, the
+ technical ID of the product in our database) so that it can be
+ safely parsed by a computer process. This is important, as
+ <literal>${product.id}</literal> would format it for human
+ audience, which may contains more complicated (like localized,
+ grouped, etc.) formatting.</para>
+ </listitem>
</itemizedlist>
<para>Some built-ins require parameters to specify the behavior
@@ -4379,19 +4386,19 @@
<para>since German people use comma as decimal separator.</para>
<warning>
- <para>As you can see, interpolations print for human audience (by
- default at least), as opposed to ''computer audience''. In some
- cases this is not good, like when you print a database record ID
- as the part of an URL or as an invisible field value in a HTML
- form, or when you print CSS/JavaScript numerical literals, because
- these printed values will be read by computer programs and not by
- humans. Most computer programs are very particular about the
- format of the numbers, and understand only a kind of simple US
- number formatting. For that, use the <link
- linkend="ref_builtin_c"><literal>c</literal></link> (stands for
- ''computer audience'') built-in, for example:</para>
+ <para>When the number you print will be read by some computer
+ process, always use the <link linkend="ref_builtin_c"><link
+ linkend="ref_builtin_c"><literal>c</literal></link>
+ built-in</link>, to get rid of any localized, or otherwise fancy
+ formatting! Like when you print a database record ID as the part
+ of an URL or as an invisible field value in a HTML form, or when
+ you print CSS/JavaScript numerical literals. Example:</para>
- <programlisting role="template"><a href="/shop/productdetails?id=${product.id?c}">Details...</a></programlisting>
+ <programlisting role="template"><a href="/shop/productdetails?id=${product.id<emphasis>?c</emphasis>}">Details...</a></programlisting>
+
+ <para>That <literal>?c</literal> there formats the number with a
+ simple US format, which most computer processes will
+ understand.</para>
</warning>
</section>
@@ -4455,7 +4462,8 @@
error.</para>
<para>When you want to generate JavaScript or other computer
- language parts, then <literal>${someBoolean?c}</literal>
+ language parts, then
+ <literal>${<replaceable>someBoolean</replaceable>?c}</literal>
(<quote>c</quote> stands for computer audience) should be used to
print true/false. (Remember that <literal>?c</literal> was also used
to print numbers for computer audience.)</para>
@@ -6013,6 +6021,131 @@
</section>
</section>
+ <section xml:id="dgui_misc_computer_vs_human_format">
+ <title>Formatting for humans, or for computers</title>
+
+ <para>By default
+ <literal>${<replaceable>someValue</replaceable>}</literal> outputs
+ numbers, date/time/date-time and boolean values in a format that
+ targets normal users (<quote>humans</quote>). You have various
+ settings to specify how that format looks, like
+ <literal>number_format</literal>, <literal>date_format</literal>,
+ <literal>time_format</literal>, <literal>datetime_format</literal>,
+ <literal>boolan_format</literal>. The output also often depends on the
+ <literal>locale</literal> setting (i.e., on the language, and country
+ of the user). So 3000000 is possibly printed as
+ <literal>3,000,000</literal> (i.e., with grouping separators), or
+ <literal>3.14</literal> is possibly printed as <literal>3,14</literal>
+ (i.e., with a different decimal separator).</para>
+
+ <para>At some places you need to output values that will be read
+ (parsed) by some program, in which case always use the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>, as in
+ <literal>${<replaceable>someValue</replaceable>?c}</literal> (the
+ <quote>c</quote> stands for Computer). <emphasis>Then the formatting
+ depends on the <literal>c_format</literal> <link
+ linkend="pgui_config_settings">setting</link></emphasis>, which
+ usually refers to a computer language, like <literal>"JSON"</literal>.
+ The output of <literal>?c</literal> is not influenced by
+ <literal>locale</literal>, <literal>number_format</literal>,
+ etc.</para>
+
+ <para>The <literal>c</literal> built-in will format string values to
+ string literals. Like if the <literal>c_format</literal> setting is
+ <literal>"JSON"</literal>, then <literal>{"fullName":
+ ${fullName?c}}</literal> will output something like
+ <literal>{"fullName": "John Doe"}</literal>, where the quotation marks
+ (and <literal>\</literal> escaping if needed) were added by
+ <literal>?c</literal>.</para>
+
+ <note>
+ <para>At least as of 2.3.32, the <literal>c</literal> built-in
+ doesn't support date/time/datetime values, only numbers, booleans,
+ and strings.</para>
+ </note>
+
+ <para>When formatting for computers, at some places you want to output
+ a <literal>null</literal> (or its equivalent in the target language)
+ if the value you print is missing/<literal>null</literal>. The
+ convenient shortcut for that is using the <literal>cn</literal>
+ built-in (the <quote>n</quote> stands for Nullable), as in
+ <literal>${<replaceable>someValue</replaceable>?cn}</literal>. It
+ behaves like the <literal>c</literal> built-in, except that if
+ <literal><replaceable>someValue</replaceable></literal> evaluates to
+ <literal>null</literal>/missing, it outputs the
+ <literal>null</literal> literal in the syntax that the
+ <literal>c_format</literal> setting specifies, instead of stopping
+ with missing value error.</para>
+
+ <para>For the templates where the output is obviously only for
+ computer consumption, some prefers setting the
+ <literal>number_format</literal>, and
+ <literal>boolean_format</literal> settings to <literal>"c"</literal>.
+ (Before 2.3.32, for <literal>number_format</literal> you have to use
+ <literal>"computer"</literal> instead.) Then
+ <literal>${<replaceable>someValue</replaceable>}</literal>, for
+ number, and boolean values will format like <literal>?c</literal>
+ does. To output string literals (i.e, to add quotation and escaping),
+ you will still need an explicit <literal>?c</literal>. Also if you
+ need to output <literal>null</literal> literals, you still have to use
+ <literal>?cn</literal>.</para>
+
+ <para>These are the table of supported <literal>c_format</literal>
+ setting values (as of FreeMarker 2.3.32), and their intended
+ use:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para><literal>"JSON"</literal>: JSON generation. Generally,
+ <literal>"JavaScript or JSON"</literal> (see later) is recommended
+ over this.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>"JavaScript"</literal>: JavaScript generation.
+ Generally, <literal>"JavaScript or JSON"</literal> (see later) is
+ recommended over this.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>"Java"</literal>: Java source code
+ generation</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>"XS"</literal>: XML Schema compliant XML
+ generation</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>"JavaScript or JSON"</literal>: For generating
+ output that's compatible with both JSON and JavaScript. This setup
+ is therefore resilient against configuration mistakes, where we
+ generate output in one language, but use the
+ <literal>c_format</literal> for the other. The small price to pay
+ is that we can't utilize some language-specific opportunities to
+ make the output a bit shorter, but that hardly matters in
+ practice. This is the default if if the <link
+ linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+ setting</link> is at least 2.3.32.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>"legacy"</literal>: Default for backward
+ compatibility when the <link
+ linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+ setting</link> is less than 2.3.32. Avoid! Can have rounding
+ losses, and formatting glitches for infinity and NaN.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>The behaviour of these is documented at the <literal>c</literal>
+ built-in: <link linkend="ref_builtin_c">for numbers</link>, <link
+ linkend="ref_builtin_c_boolean">for boolean</link>, <link
+ linkend="ref_builtin_c_string">for strings</link>, <link
+ linkend="ref_builtin_cn">for null-s</link>.</para>
+ </section>
+
<section xml:id="dgui_misc_whitespace">
<title>White-space handling</title>
@@ -6524,9 +6657,9 @@
beginning of the application (possibly servlet) life-cycle:</para>
<programlisting role="unspecified">// Create your Configuration instance, and specify if up to what FreeMarker
-// version (here 2.3.29) do you want to apply the fixes that are not 100%
+// version (here 2.3.32) do you want to apply the fixes that are not 100%
// backward-compatible. See the Configuration JavaDoc for details.
-Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
+Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// Specify the source where the template files come from. Here I set a
// plain directory for it, but non-file-system sources are possible too:
@@ -6550,7 +6683,10 @@
cfg.setWrapUncheckedExceptions(true);
// Do not fall back to higher scopes when reading a null loop variable:
-cfg.setFallbackOnNullLoopVariable(false);</programlisting>
+cfg.setFallbackOnNullLoopVariable(false);
+
+// To accomodate to how JDBC returns values; see Javadoc!
+cfg.setSQLDateAndTimeTimeZone(TimeZone.getDefault());</programlisting>
<para>From now you should use this <emphasis>single</emphasis>
configuration instance (i.e., its a singleton). Note however that if a
@@ -6822,7 +6958,7 @@
/* You should do this ONLY ONCE in the whole application life-cycle: */
/* Create and adjust the configuration singleton */
- Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setDirectoryForTemplateLoading(new File("<replaceable>/where/you/store/templates</replaceable>"));
// Recommended settings for new projects:
cfg.setDefaultEncoding("UTF-8");
@@ -6830,6 +6966,7 @@
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false);
+ cfg.setSQLDateAndTimeTimeZone(TimeZone.getDefault());
/* ------------------------------------------------------------------------ */
/* You usually do these for MULTIPLE TIMES in the application life-cycle: */
@@ -8275,10 +8412,9 @@
<literal>locale</literal>, <literal>number_format</literal>,
<literal>default_encoding</literal>,
<literal>template_exception_handler</literal>. The full list of
- settings can be found in the <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-">Java
- API documentation of
- <literal>Configuration.setSetting(...)</literal></link>.</para>
+ settings can be found in the <olink
+ targetdoc="apiConfigurationSettings">Java API documentation of
+ <literal>Configuration.setSetting(...)</literal></olink>.</para>
<para>The settings coming from the <literal>Configuration</literal>
can be overridden in a <literal>Template</literal> instance. For
@@ -10483,7 +10619,7 @@
linkend="gloss.unicode">UNICODE</link> text" (UTF-16). Nonetheless,
there are situations when it must deal with <link
linkend="gloss.charset">charsets</link>, because it has to exchange
- data with the outer world that may uses various other charsets.</para>
+ data with the outer world that may use various other charsets.</para>
<section>
<title>The charset of the input</title>
@@ -12706,7 +12842,18 @@
<listitem>
<para>c <link linkend="ref_builtin_c">for strings</link>, <link
- linkend="ref_builtin_c_boolean">for booleans</link></para>
+ linkend="ref_builtin_c_boolean">for booleans</link>, <link
+ linkend="ref_builtin_c_string">for strings</link></para>
+ </listitem>
+
+ <listitem>
+ <para><link
+ linkend="ref_builtin_c_lower_case">c_lower_case</link></para>
+ </listitem>
+
+ <listitem>
+ <para><link
+ linkend="ref_builtin_c_upper_case">c_upper_case</link></para>
</listitem>
<listitem>
@@ -12737,6 +12884,10 @@
</listitem>
<listitem>
+ <para><link linkend="ref_builtin_cn">cn</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref_builtin_contains">contains</link></para>
</listitem>
@@ -13307,6 +13458,158 @@
<para>In the case of <literal>"- green mouse"</literal>, the first
word is the <literal>-</literal>.</para>
+
+ <para>Note that this uses locale-aware conversion, that is, the
+ result can be different depending on the current
+ <literal>locale</literal> (language, country).</para>
+ </section>
+
+ <section xml:id="ref_builtin_c_string">
+ <title>c (for string value)</title>
+
+ <indexterm>
+ <primary>c built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>format</primary>
+
+ <secondary>string</secondary>
+ </indexterm>
+
+ <note>
+ <para>The <literal>c</literal> built-in also works <link
+ linkend="ref_builtin_c">on numbers</link>, and <link
+ linkend="ref_builtin_c_boolean">on booleans</link>!</para>
+ </note>
+
+ <note>
+ <para>To provide a background, see <xref
+ linkend="dgui_misc_computer_vs_human_format"/></para>
+ </note>
+
+ <note>
+ <para>The <literal>c</literal> built-in supports strings since
+ FreeMarker 2.3.32.</para>
+ </note>
+
+ <para>This built-in converts a string to a <quote>computer
+ language</quote> literal, according the value of the <link
+ linkend="gloss.c_format"><literal>c_format</literal>
+ setting</link>.</para>
+
+ <para>For the <literal>c_format</literal>-s that are built into
+ FreeMarker the rules are the following:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para><quote>JSON</quote>, <quote>legacy</quote>: Gives a JSON
+ string literal, that is, it will be surrounded with quotation
+ marks (<literal>"</literal>), and will be escaped using
+ backslash (<literal>\</literal>) where needed. For the exact
+ escaping rules see <link linkend="ref_builtin_json_string">the
+ <literal>json_string</literal> built-in</link>.</para>
+ </listitem>
+
+ <listitem>
+ <para><quote>JavaScript</quote>: Almost the same as JSON, but
+ uses <literal>\x<replaceable>XX</replaceable></literal> instead
+ of <literal>\u<replaceable>XXXX</replaceable></literal> where
+ possible. For the exact escaping rules see <link
+ linkend="ref_builtin_js_string">the <literal>js_string</literal>
+ built-in</link>, except that this won't escape the apostrophe
+ quote (since it knows that it has used quotation marks around
+ the string literal).</para>
+ </listitem>
+
+ <listitem>
+ <para><quote>Java</quote>: Gives a Java string literal, that is,
+ it will be surrounded with quotation marks
+ (<literal>"</literal>), and will be escaped using backslash
+ (<literal>\</literal>) where needed. For the exact escaping
+ rules see <link linkend="ref_builtin_j_string">the
+ <literal>j_string</literal> built-in</link>.</para>
+ </listitem>
+
+ <listitem>
+ <para><quote>XS</quote>: Leaves the string as is. The idea is
+ that you will insert the value as the body of an XML element, or
+ into an XML attribute, so it needs no quotation, or any other
+ special syntax. While it does need XML encoding, that should be
+ handled by the <link linkend="dgui_misc_autoescaping">automatic
+ escaping facility</link>, and not by the
+ <literal>c_format</literal> facility.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>If the value the <literal>c</literal> built-in is applied on
+ is <literal>null</literal>/missing, it will stop the template
+ processing with error, just like most other built-ins. If instead
+ you want to output a <literal>null</literal> literal, see the <link
+ linkend="ref_builtin_cn_string"><literal>cn</literal>
+ built-in</link>.</para>
+ </section>
+
+ <section xml:id="ref_builtin_cn_string">
+ <title>cn (for string value)</title>
+
+ <indexterm>
+ <primary>cn built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>format</primary>
+
+ <secondary>string</secondary>
+ </indexterm>
+
+ <para>This does the same as the <link
+ linkend="ref_builtin_c_string"><literal>c</literal> built-in</link>,
+ but when applied on a <literal>null</literal>/missing value, it will
+ output a <literal>null</literal> value according the <link
+ linkend="gloss.c_format"><literal>c_format</literal> setting</link>.
+ See more details about formatting a <literal>null</literal> <link
+ linkend="ref_builtin_cn">here</link>.</para>
+ </section>
+
+ <section xml:id="ref_builtin_c_lower_case">
+ <title>c_lower_case</title>
+
+ <indexterm>
+ <primary>c_lower_case built-in</primary>
+ </indexterm>
+
+ <para>The string converted to lower case, for computer consumption
+ (<quote>c</quote> as in the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>). Put
+ simply, it will be converted to lower case as in English, regardless
+ of the current <literal>locale</literal> (language, country). For
+ example <literal>"ITEM list"?c_lower_case</literal> will be
+ <literal>"item list"</literal>, always.</para>
+
+ <para>For lower case conversion for human consumption use the <link
+ linkend="ref_builtin_lower_case"><literal>lower_case</literal>
+ built-in</link> instead!</para>
+ </section>
+
+ <section xml:id="ref_builtin_c_upper_case">
+ <title>c_upper_case</title>
+
+ <indexterm>
+ <primary>c_upper_case built-in</primary>
+ </indexterm>
+
+ <para>The string converted to upper case, for computer consumption
+ (<quote>c</quote> as in the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>). Put
+ simply, it will be converted to upper case as in English, regardless
+ of the current <literal>locale</literal> (language, country). For
+ example <literal>"ITEM list"?c_upper_case</literal> will be
+ <literal>"ITEM LIST"</literal>, always.</para>
+
+ <para>For upper case conversion for human consumption use the <link
+ linkend="ref_builtin_upper_case"><literal>upper_case</literal>
+ built-in</link> instead!</para>
</section>
<section xml:id="ref_builtin_capitalize">
@@ -13891,10 +14194,11 @@
JSON strings must be quoted with <literal>"</literal>.</para>
<para>The escaping rules are almost identical to those <link
- linkend="ref_builtin_j_string">documented for
+ linkend="ref_builtin_js_string">documented for
<literal>js_string</literal></link>. The differences are that
- <literal>'</literal> is not escaped at all, that > is escaped as
- \u003E (not as \>), and that
+ <literal>'</literal> is not escaped at all, that
+ <literal>></literal> is escaped as <literal>\u003E</literal> (not
+ as <literal>\></literal>), and that
<literal>\u<replaceable>XXXX</replaceable></literal> escapes are
used instead of <literal>\x<replaceable>XX</replaceable></literal>
escapes.</para>
@@ -14151,9 +14455,18 @@
<primary>lower_case built-in</primary>
</indexterm>
- <para>The lower case version of the string. For example
- <literal>"GrEeN MoUsE"?lower_case</literal> will be <literal>"green
- mouse"</literal>.</para>
+ <para>The lower case version of the string, using rules that depend
+ on the current <literal>locale</literal> (language, country). For
+ example <literal>"KARIŞIK işaretler"?lower_case</literal> will be
+ <literal>"karişik işaretler"</literal> in most locales, but will be
+ <literal>"karışık işaretler"</literal> in Turkish
+ (<literal>tr_TR</literal>) locale (note the missing dot above some
+ of the <quote>i</quote>-s).</para>
+
+ <para>To convert to lower case for computer consumption (as opposed
+ to human consumption), use the <link
+ linkend="ref_builtin_c_lower_case"><literal>c_lower_case</literal>
+ built-in</link> instead!</para>
</section>
<section xml:id="ref_builtin_matches">
@@ -15006,8 +15319,10 @@
<para>The opposite of <link
linkend="ref_builtin_cap_first"><literal>cap_first</literal></link>.
- The string with the very first word of the string
- un-capitalized.</para>
+ The string with the very first word of the string un-capitalized.
+ Note that this uses locale-aware conversion, that is, the result can
+ be different depending on the current <literal>locale</literal>
+ (language, country).</para>
</section>
<section xml:id="ref_builtin_upper_case">
@@ -15017,9 +15332,17 @@
<primary>upper_case built-in</primary>
</indexterm>
- <para>The upper case version of the string. For example
- <literal>"GrEeN MoUsE"</literal> will be <literal>"GREEN
- MOUSE"</literal>.</para>
+ <para>The upper case version of the string, using rules that depend
+ on the current <literal>locale</literal> (language, country). For
+ example <literal>"KARIŞIK işaretler"?upper_case</literal> will be
+ <literal>"KARIŞIK IŞARETLER"</literal> in most locales, but with
+ Turkish locale it will be <literal>"KARIŞIK İŞARETLER"</literal>
+ (note the dot above the 2nd <quote>I</quote>).</para>
+
+ <para>To convert to upper case for computer consumption (as opposed
+ to human consumption), use the <link
+ linkend="ref_builtin_c_upper_case"><literal>c_upper_case</literal>
+ built-in</link> instead!</para>
</section>
<section xml:id="ref_builtin_url">
@@ -15628,7 +15951,7 @@
</section>
<section xml:id="ref_builtin_c">
- <title>c (when used with numerical value)</title>
+ <title>c (for numbers)</title>
<indexterm>
<primary>c built-in</primary>
@@ -15649,54 +15972,319 @@
</indexterm>
<note>
- <para>This built-in exists since FreeMarker 2.3.3.</para>
+ <para>The <literal>c</literal> built-in also works <link
+ linkend="ref_builtin_c_boolean">on booleans</link>, and <link
+ linkend="ref_builtin_c_string">on strings</link>!</para>
</note>
- <para>This built-in converts a number to string for a
- <quote>computer language</quote> as opposed to for human audience.
- That is, it formats with the rules that programming languages used
- to use, which is independent of all the locale and number format
- settings of FreeMarker. It always uses dot as decimal separator, and
- it never uses grouping separators (like 3,000,000), nor exponential
- form (like 5E20), nor superfluous leading or trailing 0-s (like 03
- or 1.0), nor + sign (like +1). It will print at most 16 digits after
- the decimal dot, and thus numbers whose absolute value is less than
- 1E-16 will be shown as 0. This built-in is crucial because be
- default (like with <literal>${x}</literal>) numbers are converted to
- strings with the locale (language, country) specific number
- formatting, which is for human readers (like 3000000 is possibly
- printed as 3,000,000). When the number is printed not for human
- audience (e.g., for a database record ID used as the part of an URL,
- or as invisible field value in a HTML form, or for printing
- CSS/JavaScript numerical literals) this built-in must be used to
- print the number (i.e., use <literal>${x?c}</literal> instead of
- <literal>${x}</literal>), or else the output will be possibly broken
- depending on the current number formatting settings and locale (like
- the decimal point is not dot, but comma in many countries) and the
- value of the number (like big numbers are possibly
- <quote>damaged</quote> by grouping separators).</para>
+ <note>
+ <para>To provide a background, see <xref
+ linkend="dgui_misc_computer_vs_human_format"/></para>
+ </note>
- <para>If the <literal>incompatible_improvements</literal> FreeMarker
- configuration setting is set to 2.3.24 or higher (also if it's set
- to 2.3.20 or higher and you are outside a string literal), this
- built-in will return <literal>"INF"</literal>,
- <literal>"-INF"</literal> and <literal>"NaN"</literal> for
- positive/negative infinity and IEEE floating point Not-a-Number,
- respectively. These are the XML Schema compatible representations of
- these special values. (Earlier it has returned what
- <literal>java.text.DecimalFormat</literal> did with US locale, none
- of which is understood by any (common) computer language.)</para>
+ <para>This built-in converts a number to a <quote>computer
+ language</quote> literal, as opposed to format it for human reading.
+ This format is independent of the <literal>locale</literal> (human
+ language, country) and <literal>number_format</literal> settings of
+ FreeMarker. Instead, it depends on the <link
+ linkend="gloss.c_format"><literal>c_format</literal> setting</link>,
+ which is usually something like <literal>"JSON"</literal>; a
+ computer language.</para>
- <para>Note that this built-in <link
- linkend="ref_builtin_c_boolean">also works on
- booleans</link>.</para>
+ <para>This built-in is crucial because by default (like with
+ <literal>${x}</literal>) numbers are converted to strings with the
+ locale specific number formatting, so 3000000 is possibly printed as
+ <literal>3,000,000</literal> (i.e., with grouping separators), or
+ <literal>3.14</literal> is possibly printed as
+ <literal>3,14</literal> (i.e., with a different decimal separator).
+ When the number is printed not for human audience (e.g., for a
+ database record ID used as the part of an URL, or as invisible field
+ value in a HTML form, or for printing CSS/JavaScript numerical
+ literals) you must use this built-in to format the number (i.e., use
+ <literal>${x?c}</literal> instead of <literal>${x}</literal>), or
+ else the output will be possibly unparsable for the consumer.</para>
+
+ <para>The exact format of numbers depend on value of the
+ <literal>c_format</literal> setting, but for all the
+ <literal>c_format</literal>-s that are built into FreeMarker, these
+ sand:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>It always uses dot as decimal separator</para>
+ </listitem>
+
+ <listitem>
+ <para>It always uses dot as decimal separator</para>
+ </listitem>
+
+ <listitem>
+ <para>Never uses <quote>+</quote> sign (as in +1), except maybe
+ after the <quote>E</quote> that signifies the exponent
+ part</para>
+ </listitem>
+
+ <listitem>
+ <para>No superfluous leading or trailing 0-s (like 03 or
+ 1.0)</para>
+ </listitem>
+
+ <listitem>
+ <para>It usually avoids exponential form (like it will never
+ format 10000000000 as 1E10), but see the details below regarding
+ that.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>Finder details:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Rounding:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>For all that are built into FreeMarker, except
+ <quote>legacy</quote> <literal>c_format </literal>: There's
+ no rounding.</para>
+ </listitem>
+
+ <listitem>
+ <para>For the deprecated <literal>c_format</literal>,
+ <quote>legacy</quote>: The numbers are limited to 16 digits
+ after the decimal dot, so rounding can occur. The these
+ formats never use exponential form either, so the decimal
+ point place is fixed. Thus, for example, 1E-17 will be
+ formatted as <literal>0</literal>.</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>Special floating point values, positive infinity, negative
+ infinity, and NaN (for Not-a-Number):</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>For <literal>c_format</literal>-s <quote>JSON</quote>,
+ and <quote>JavaScript</quote>, <quote>JavaScript or
+ JSON</quote>: <literal>Infinity</literal>,
+ <literal>-Infinity</literal>, <literal>NaN</literal></para>
+ </listitem>
+
+ <listitem>
+ <para>For <literal>c_format</literal> <quote>Java</quote>:
+ If the value has Java type <literal>double</literal> or
+ <literal>Double</literal>:
+ <literal>Double.POSITIVE_INFINITY</literal>,
+ <literal>Double.NEGATIVE_INFINITY</literal>,
+ <literal>Double.NaN</literal>. If the value has Java type
+ <literal>float</literal> or <literal>Float</literal>:
+ <literal>Float.POSITIVE_INFINITY</literal>,
+ <literal>Float.NEGATIVE_INFINITY</literal>,
+ <literal>Float.NaN</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>For <literal>c_format</literal> <quote>XS</quote>, and
+ also if <link
+ linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+ setting</link> is at least 2.3.21, for
+ <literal>c_format</literal>, <quote>legacy</quote>:
+ <literal>INF</literal>, <literal>-INF</literal>, and
+ <literal>NaN</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>For <literal>c_format</literal> <quote>legacy</quote>,
+ if <link
+ linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+ setting</link> is less than 2.3.21: Gives what
+ <literal>java.text.DecimalFormat</literal> does with US
+ locale, which are <literal>∞</literal>,
+ <literal>-∞</literal>, and <literal>�</literal> (U+FFFD,
+ replacement character).</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>Exponential form is used by all
+ <literal>c_format</literal>-s that are built into FreeMarker,
+ except <quote>legacy</quote>:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>For a non-whole number whose absolute value is less
+ than 1E-6 (0.000001).</para>
+ </listitem>
+
+ <listitem>
+ <para>For a whole number that has more than 100 digits in
+ non-exponential form</para>
+ </listitem>
+
+ <listitem>
+ <para>For a whole numbers whose absolute value is too big
+ for the backing floating point type to be safe from rounding
+ errors. More specifically:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>For a whole number that's stored in a
+ <literal>double</literal>, or <literal>Double</literal>
+ on the Java side (i.e., as 64 bit floating point
+ number), and has an absolute value greater than
+ 9007199254740992. It's because that type can't store all
+ whole numbers outside that range. So if the intent was
+ to store some ID-s, they are likely already corrupted
+ (because they had to be rounded to the closest whole
+ number that the type can store), and you should use
+ <literal>long</literal> or <literal>BigInteger</literal>
+ on the Java side.</para>
+ </listitem>
+
+ <listitem>
+ <para>For a whole (integer) value that's stored in a
+ <literal>float</literal>, or <literal>Float</literal> on
+ the Java side (i.e., as 32 bit floating point number),
+ and has an absolute value greater than 16777216.
+ Reasoning is the same as for
+ <literal>double</literal>.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>Note that by default FreeMarker doesn't use
+ <literal>double</literal> or <literal>float</literal>, so
+ such values are likely come from the data-model.</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para>Currently, in the <literal>c_format</literal>-s that are
+ built into FreeMarker, the output never contains superfluous
+ zeros after the decimal point. Thus, unlike in Java, you can't
+ tell apart a <literal>double</literal> from an
+ <literal>int</literal>, if they store the same number
+ mathematically, because the output will look the same for both.
+ (This is because the template language only have a single number
+ type, so you don't have a good control over the backing Java
+ type.)</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>If you only generate output that's computer language and isn't
+ read by end-users, you may prefer to set the
+ <literal>number_format</literal> configuration setting to
+ <literal>"c"</literal> (since FreeMarker 2.3.32,
+ <literal>"computer"</literal> before that), in which case
+ <literal>${<replaceable>aNumber</replaceable>}</literal> will have
+ the same output as
+ <literal>${<replaceable>aNumber</replaceable>?c}</literal>. (In this
+ case you should use a <literal>c_format</literal> like
+ <literal>"JavaScript or JSON"</literal>, and not some of the
+ strictly backward compatible defaults, as those are emulating some
+ confusing old glitches.)</para>
+
+ <para>If the value the <literal>c</literal> built-in is applied on
+ is <literal>null</literal>/missing, it will stop the template
+ processing with error, just like most other built-ins. If instead
+ you want to output a <literal>null</literal> literal, see the <link
+ linkend="ref_builtin_cn"><literal>cn</literal>
+ built-in</link>.</para>
+ </section>
+
+ <section xml:id="ref_builtin_cn">
+ <title>cn (for numbers)</title>
+
+ <indexterm>
+ <primary>cn built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>type-casting</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>converting between types</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>format</primary>
+
+ <secondary>number</secondary>
+ </indexterm>
+
+ <note>
+ <para><literal>cn</literal> works with all types that
+ <literal>c</literal> does, and thus for strings and booleans as
+ well. The formatting of <literal>null</literal>/missing doesn't
+ depend on the type of course (as we have no value that could have
+ a type).</para>
+ </note>
+
+ <note>
+ <para>See <xref linkend="dgui_misc_computer_vs_human_format"/> for
+ background</para>
+ </note>
+
+ <note>
+ <para>This built-in exists since FreeMarker 2.3.32</para>
+ </note>
+
+ <para>This is the same as the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>, except
+ if the value on its left side is <literal>null</literal>/missing,
+ this won't stop with error, but outputs a <literal>null</literal>
+ literal that's appropriate for the current
+ <literal>c_format</literal> setting:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>For <quote>JSON</quote>, <quote>Java</quote>,
+ <quote>JavaScript</quote>, and <quote>legacy</quote>:
+ <literal>null</literal></para>
+ </listitem>
+
+ <listitem>
+ <para>For <quote>XS</quote> (used for generating XML that
+ follows XML Schema principles): 0 length string (i.e.,
+ <literal>${<replaceable>thisIsNull</replaceable>?cn}</literal>
+ prints nothing), which is often not good enough (see below), but
+ we can't do better with a
+ <literal><replaceable>stringValue</replaceable>?c</literal>
+ alone. The idea is that you write something like
+ <literal><full-name>${fullName?nc}</full-name></literal>,
+ or <literal><user <replaceable>...</replaceable>
+ full-name="${fullName?nc}" /></literal>, and then, in case
+ <literal>fullName</literal> is <literal>null</literal>, the
+ output will be
+ <literal><full-name></full-name></literal>, and
+ <literal><user <replaceable>...</replaceable> full-name=""
+ /></literal>. Some applications accept that as the equivalent
+ of a <literal>null</literal>, at least where a string value is
+ expected according the XML Schema.</para>
+
+ <para>Note that the XML Schema approach is that you skip
+ outputting the whole <literal>full-name</literal> XML element,
+ or XML attribute. For that you have to write <literal><#if
+ fullName??><full-name>${full-name?c}</full-name></#if></literal>,
+ and <literal><user <replaceable>...</replaceable> <#if
+ fullName??>full-name="${fullName?c}"</#if>
+ /></literal>. When using such condition, and the value is a
+ string (as with this example), you might as well just write
+ <literal>${fullName}</literal>, without the
+ <literal>?c</literal>.</para>
+ </listitem>
+ </itemizedlist>
</section>
<section xml:id="ref_builtin_is_infinite">
<title>is_infinite</title>
<indexterm>
- <primary>is_infinte built-in</primary>
+ <primary>is_infinite built-in</primary>
</indexterm>
<indexterm>
@@ -15885,14 +16473,15 @@
configuration settings. You can also specify a number format
explicitly with this built-in, as it will be shown later.</para>
- <para>There are four predefined number formats:
- <literal>computer</literal>, <literal>currency</literal>,
- <literal>number</literal>, and <literal>percent</literal>. The exact
- meaning of these is locale (nationality) specific, and is controlled
- by the Java platform installation, not by FreeMarker, except for
- <literal>computer</literal>, which uses the same formatting as <link
- linkend="ref_builtin_c">the <literal>c</literal> built-in</link>
- (assuming <link
+ <para>There are four predefined number formats: <literal>c</literal>
+ (since 2.3.32, before that it was called
+ <literal>computer</literal>, which still works),
+ <literal>currency</literal>, <literal>number</literal>, and
+ <literal>percent</literal>. The exact meaning of these is locale
+ (nationality) specific, and is controlled by the Java platform
+ installation, not by FreeMarker, except for <literal>c</literal>,
+ which uses the same formatting as <link linkend="ref_builtin_c">the
+ <literal>c</literal> built-in</link> (assuming <link
linkend="pgui_config_incompatible_improvements">incompatible
improvements</link> set to 2.3.31, or higher, or else infinity and
NaN isn't formatted like that). There can also be programmer-defined
@@ -15906,7 +16495,7 @@
${x?string.number}
${x?string.currency}
${x?string.percent}
-${x?string.computer}</programlisting>
+${x?string.c} <#-- But you should write ${x?c} --></programlisting>
<para>If your locale is US English, this will print:</para>
@@ -16832,7 +17421,7 @@
</indexterm>
<section xml:id="ref_builtin_c_boolean">
- <title>c (when used with boolean)</title>
+ <title>c (for boolean value)</title>
<indexterm>
<primary>c built-in</primary>
@@ -16853,28 +17442,73 @@
</indexterm>
<note>
- <para>This built-in exists since FreeMarker 2.3.20.</para>
+ <para>The <literal>c</literal> built-in also works <link
+ linkend="ref_builtin_c_boolean"> <link linkend="ref_builtin_c">on
+ numbers</link></link>, and <link linkend="ref_builtin_c_string">on
+ strings</link>!</para>
</note>
- <para>This built-in converts a boolean to string for a
- <quote>computer language</quote> as opposed to for human audience.
- The result will be <literal>"true"</literal> or
- <literal>"false"</literal>, regardless of the
- <literal>boolean_format</literal> configuration setting, as that
- setting is meant to specify the format for human consumption. When
- generating boolean literals for JavaScript and such, this should be
- used.</para>
+ <note>
+ <para>To provide a background, see <xref
+ linkend="dgui_misc_computer_vs_human_format"/></para>
+ </note>
+
+ <note>
+ <para>The <literal>c</literal> built-in supports booleans since
+ FreeMarker 2.3.20.</para>
+ </note>
+
+ <para>This built-in converts a boolean to a <quote>computer
+ language</quote> literal, as opposed to format it for human reading.
+ This formats is independent of the <literal>boolean_format</literal>
+ configuration setting, as that setting is meant to specify the
+ format for human readers. Instead, it depends on the <link
+ linkend="gloss.c_format"><literal>c_format</literal> setting</link>.
+ However, currently all <literal>c_format</literal> that's built into
+ FreeMarker result will give <literal>true</literal> or
+ <literal>false</literal>.</para>
+
+ <para>When outputting boolean literals for JavaScript, JSON, Java,
+ and many other languages, always use this built-in, instead of
+ relying on <literal>boolean_format</literal>.</para>
<para>If you only generate output that's computer language and isn't
- read by end-users, you may want to set the
+ read by end-users, you may prefer to set the
<literal>boolean_format</literal> configuration setting to
<literal>c</literal> (since FreeMarker 2.3.29), in which case
<literal>${<replaceable>aBoolean</replaceable>}</literal> will have
the same output as
<literal>${<replaceable>aBoolean</replaceable>?c}</literal>.</para>
- <para>Note that this built-in <link linkend="ref_builtin_c">also
- works on strings</link>.</para>
+ <para>If the value the <literal>c</literal> built-in is applied on
+ is <literal>null</literal>/missing, it will stop the template
+ processing with error, just like most other built-ins. If instead
+ you want to output a <literal>null</literal> literal, see the <link
+ linkend="ref_builtin_cn_string"><literal>cn</literal>
+ built-in</link>.</para>
+ </section>
+
+ <section xml:id="ref_builtin_cn_boolean">
+ <title>cn (for boolean value)</title>
+
+ <indexterm>
+ <primary>cn built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>format</primary>
+
+ <secondary>boolean</secondary>
+ </indexterm>
+
+ <para>This does the same as the <link
+ linkend="ref_builtin_c_boolean"><literal>c</literal>
+ built-in</link>, but when applied on a
+ <literal>null</literal>/missing value, it will output a
+ <literal>null</literal> value according the <link
+ linkend="gloss.c_format"><literal>c_format</literal> setting</link>.
+ See more details about formatting a <literal>null</literal> <link
+ linkend="ref_builtin_cn">here</link>.</para>
</section>
<section xml:id="ref_builtin_string_for_boolean">
@@ -17053,10 +17687,6 @@
<primary>columnar printing of sequences</primary>
</indexterm>
- <note>
- <para>This built-in exists since FreeMarker 2.3.3.</para>
- </note>
-
<para>This built-in splits a sequence into multiple sequences of the
size given with the 1st parameter to the built-in (like
<literal>mySeq?chunk(3)</literal>). The result is the sequence of
@@ -23727,9 +24357,7 @@
<section>
<title>Synopsis</title>
- <programlisting role="metaTemplate">
-<literal><#setting <replaceable>name</replaceable>=<replaceable>value</replaceable>></literal>
-</programlisting>
+ <programlisting role="metaTemplate"><literal><#setting <replaceable>name</replaceable>=<replaceable>value</replaceable>></literal></programlisting>
<para>Where:</para>
@@ -23801,9 +24429,10 @@
</listitem>
<listitem>
- <para><literal>computer</literal>, which formats like <link
- linkend="ref_builtin_c">the <literal>c</literal>
- built-in</link></para>
+ <para><literal>c</literal> since 2.3.24 (was called
+ <literal>computer</literal> before that, which still works),
+ which formats like <link linkend="ref_builtin_c">the
+ <literal>c</literal> built-in</link></para>
</listitem>
<listitem>
@@ -24160,6 +24789,18 @@
<listitem>
<para><indexterm>
+ <primary>c_format</primary>
+ </indexterm><literal>c_format</literal> (since FreeMarker
+ 2.3.32): Sets what format to use when formatting for computer
+ consumption, like <literal>"JavaScript or JSON"</literal>.
+ Mostly prominently this affects the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>,
+ hence the name. See valid values and their meaning here: <xref
+ linkend="dgui_misc_computer_vs_human_format"/>.</para>
+ </listitem>
+
+ <listitem>
+ <para><indexterm>
<primary>url_escaping_charset</primary>
</indexterm><literal>url_escaping_charset</literal>: The
charset used for URL escaping (e.g. for
@@ -28160,7 +28801,7 @@
<answer>
<para>FreeMarker uses the locale-sensitive number formatting
capability of the Java platform. The default number format for
- your locale may uses grouping or other formatting. If you don't
+ your locale may use grouping or other formatting. If you don't
want that, you have to override the number format suggested by the
Java platform with the <literal>number_format</literal> <link
linkend="pgui_config_settings">FreeMarker setting</link>. For
@@ -29425,13 +30066,156 @@
<itemizedlist>
<listitem>
- <para>This is related to <literal>freemarker.ext.xml</literal>,
- which is the very old, long deprecated XML wrapper (the commonly
- used one is <literal>freemarker.ext.dom</literal>), which almost
- nobody uses, or maybe truly nobody. Still, now the
- <literal>_registerNamespace</literal> key works, doing what the
- documentation always stated. Before this fix it just behaved as
- if it was the name of an element you are looking for.</para>
+ <para>Improved outputting values for computer/parser consumption
+ (such as for generating JSON, JavaScript values, values encoded
+ into URL-s):</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Added new configuration setting, <link
+ linkend="gloss.c_format"><literal>c_format</literal></link>
+ (also settable via the <link
+ linkend="ref.directive.setting"><literal>setting</literal>
+ directive</link>). This specifies what syntax to use when
+ formatting values for computer consumption/parser, like
+ <literal>"JSON"</literal>. Most prominently, this affects
+ the <link linkend="ref_builtin_c"><literal>c</literal>
+ built-in</link>, hence the name. See valid setting values,
+ and their meaning here: <xref
+ linkend="dgui_misc_computer_vs_human_format"/>.</para>
+
+ <para>If you set the <link
+ linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatible_improvements</literal>
+ setting</link> to 2.3.32, the default of
+ <literal>c_format</literal> changes from
+ <literal>"legacy"</literal> to <literal>"JavaScript or
+ JSON"</literal>, which is a format that most targets can
+ parse (because we just format simple values, not lists and
+ maps). With lower
+ <literal>incompatible_improvements</literal>, the default
+ value is <literal>"legacy"</literal> that emulates the old
+ behavior of <literal>?c</literal> (where you can lose
+ numerical prevision, etc.), so it's recommended to set it to
+ something else.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal><link
+ linkend="ref_builtin_c">?c</link></literal> now formats
+ string values to string literals (with quotation marks and
+ escaping), according the language specified in the <link
+ linkend="gloss.c_format"><literal>c_format</literal>
+ setting</link>, such as <literal>JSON</literal>,
+ <literal>Java</literal>, etc. Earlier, <literal>?c</literal>
+ only allowed numbers, and booleans.</para>
+
+ <para>To generate JSON, you can now write a piece of
+ template like this:</para>
+
+ <programlisting role="template">"fullName": ${user.fullName?c},</programlisting>
+
+ <para>Then the output will be like:</para>
+
+ <programlisting role="output">"fullName": "John Doe",</programlisting>
+
+ <para>Note that the quotation marks were added by
+ <literal>?c</literal>, and weren't typed into the
+ template.</para>
+ </listitem>
+
+ <listitem>
+ <para>Added <link linkend="ref_builtin_cn">?cn</link>, which
+ is like <literal><link
+ linkend="ref_builtin_c">?c</link></literal>, except if the
+ value is <literal>null</literal>/missing, it will output a
+ <literal>null</literal> literal, according the language
+ specified in the new <link
+ linkend="gloss.c_format"><literal>c_format</literal>
+ setting</link>.</para>
+
+ <para>Let's say, in the previous example
+ <literal>user.fullName</literal> is expected to be
+ <literal>null</literal> sometimes. Then you can just use
+ <literal>?cn</literal> instead if
+ <literal>?c</literal>:</para>
+
+ <programlisting role="template">"fullName": ${user.fullName?cn},</programlisting>
+
+ <para>If said variable is <literal>null</literal>, the
+ output will be like this (otherwise it will be a quoted
+ string like earlier):</para>
+
+ <programlisting role="output">"fullName": null,</programlisting>
+
+ <para>Note with this approach you don't complicate the
+ template anymore to avoid printing quotation marks. Of
+ course, <literal>?cn</literal> works on numerical and
+ boolean values as well.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>c_format</literal>-s which are not merely for
+ backward compatibility, the number formatting changes
+ relatively to how <literal>?c</literal> did it in earlier
+ versions. The change affects some non-whole numbers, and
+ whole numbers with over 100 digits. The goal of this change
+ is to make the formatting lossless, and also to avoiding
+ huge output with exponents of high magnitude. See details at
+ the documentation of the <link
+ linkend="ref_builtin_c"><literal>c</literal>
+ built-in</link>.</para>
+
+ <para>Setting the <link
+ linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatible_improvements</literal>
+ setting</link> to 2.3.32 will change the default of
+ <literal>c_format</literal> to <literal>"JavaScript or
+ JSON"</literal>, which has these number formatting changes
+ (just like all <literal>c_format</literal>-s that aren't
+ just to emulate old behavior have). But this only affects
+ number formatting done with <literal>?c</literal>,
+ <literal>?cn</literal>, and with <quote>c</quote>
+ <literal>number_format</literal>, and not number formatting
+ in general.</para>
+ </listitem>
+
+ <listitem>
+ <para>For consistency, when setting the
+ <literal>number_format</literal> setting (also when
+ formatting with
+ <literal>?string(<replaceable>format</replaceable>)</literal>),
+ now <literal>"c"</literal> can be used instead of
+ <quote>computer</quote>. Both has the same effect on
+ formatting, but <literal>"c"</literal> is preferred from now
+ on.</para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+
+ <listitem>
+ <para><link
+ xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-208">FREEMARKER-208</link>:
+ Added <link
+ linkend="ref_builtin_c_lower_case"><literal>?c_lower_case</literal></link>,
+ and <link
+ linkend="ref_builtin_c_upper_case"><literal>?c_upper_case</literal></link>,
+ which are the non-localized (computer language) variants of
+ <literal>?lower_case</literal>, and
+ <literal>?upper_case</literal>. The primary problem people run
+ into with the localized versions is that with Turkish locale the
+ letter <literal>i</literal>, and <literal>I</literal> has
+ different conversions as in most languages, which causes problem
+ if the conversion was for computer consumption (for technical
+ purposes), and not for humans.</para>
+ </listitem>
+
+ <listitem>
+ <para>In <literal>freemarker.ext.xml</literal>, which is the
+ old, long deprecated XML wrapper, that almost nobody uses
+ anymore (the commonly used one is
+ <literal>freemarker.ext.dom</literal>), the
+ <literal>_registerNamespace</literal> key now works, doing what
+ the documentation always stated. Before this fix it just behaved
+ as if it was the name of an element you are looking for.</para>
</listitem>
</itemizedlist>
</section>
@@ -29441,6 +30225,49 @@
<itemizedlist>
<listitem>
+ <para>Added <literal>Configurable.setCFormat(CFormat)</literal>,
+ with the usual accompanying setting API methods/constants. See
+ the <literal>c_format</literal>-, and the
+ <literal>?c</literal>-related changes earlier in the FTL
+ section.</para>
+ </listitem>
+
+ <listitem>
+ <para>Added
+ <literal>Environment.getCTemplateNumberFormat()</literal> that
+ returns a
+ <literal>freemarker.core.TemplateNumberFormat</literal>, and
+ deprecated <literal>getCNumberFormat()</literal> that returns a
+ <literal>java.text.NumberFormat</literal>. The new
+ <literal>?c</literal> behavior (see earlier) is only reflected
+ by the new method, and what the deprecated methods returns is
+ stuck at 2.3.31 <literal>incompatible_improvements</literal>
+ mode.</para>
+ </listitem>
+
+ <listitem>
+ <para>Improved <literal>StringUtil.jsStringEnc</literal> and
+ <literal>javaSctringEsc</literal> to support quoting. Also
+ <literal>jsStringEnc</literal> now have a mode that targets both
+ JavaScript and JSON, and doesn't give up apostrophe
+ escaping.</para>
+ </listitem>
+
+ <listitem>
+ <para><link
+ xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-198">FREEMARKER-198</link>:
+ Fixed possible deadlock when the
+ <literal>Configuration</literal> and
+ <literal>DefaultObjectWrapper</literal> class
+ <emphasis>static</emphasis> initialization is triggered in
+ different threads around the same time. (In the very unlikely
+ case your application can run into this, this will hang the code
+ that initializes FreeMarker before it has processed any
+ templates, and it can't happen anymore if any template
+ processing managed to start.)</para>
+ </listitem>
+
+ <listitem>
<para><link
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-190">FREEMARKER-190</link>:
Updated dom4j version used during FreeMarker project compilation
@@ -42357,6 +43184,36 @@
</glossdef>
</glossentry>
+ <glossentry xml:id="gloss.c">
+ <glossterm>c</glossterm>
+
+ <glossdef>
+ <para>In the context of FreeMarker, this usually refers to the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>, or
+ <quote>computer format</quote> in general.</para>
+ </glossdef>
+ </glossentry>
+
+ <glossentry xml:id="gloss.c_format">
+ <glossterm>c_format</glossterm>
+
+ <glossdef>
+ <para><literal>c_format</literal> (aka. <literal>cFormat</literal>,
+ <quote>c format</quote>) is a <link
+ linkend="pgui_config_settings">configuration setting</link> that
+ specifies how to format values for <quote>computer
+ consumption</quote>, that is, if the target is some computer language,
+ or other simpler parser, and not a human reader. The value of this
+ setting is usually a computer language, like <quote>JSON</quote>, or
+ <quote>Java</quote>. The name refers to the <link
+ linkend="ref_builtin_c"><literal>c</literal> built-in</link>, which is
+ the typical way of formatting simple values for such output (e.g.
+ <literal><a href="/product/${product.id?c}"></literal>). To
+ understand the topic more, see: <xref
+ linkend="dgui_misc_computer_vs_human_format"/></para>
+ </glossdef>
+ </glossentry>
+
<glossentry xml:id="gloss.character">
<glossterm>Character</glossterm>
diff --git a/src/manual/en_US/docgen.cjson b/src/manual/en_US/docgen.cjson
index e124f3e..21adabc 100644
--- a/src/manual/en_US/docgen.cjson
+++ b/src/manual/en_US/docgen.cjson
@@ -18,7 +18,9 @@
// under the License.
deployUrl: "https://freemarker.apache.org/docs/"
-onlineTrackerHTML: "docgen-misc/googleAnalytics.html"
+// ASF policy doesn't allow using Google Analytics anymore:
+// onlineTrackerHTML: "docgen-misc/googleAnalytics.html"
+// Search still uses cookies:
cookieConsentScriptURL: "https://cdn.jsdelivr.net/npm/cookie-bar/cookiebar-latest.min.js"
searchKey: "003127866208504630097:arjqbv_znfw"
validation: {
@@ -58,6 +60,7 @@
olinks: {
homepage: "https://freemarker.apache.org/"
api: "api/index.html"
+ apiConfigurationSettings: "api/freemarker/core/Configurable.html#setSetting-java.lang.String-java.lang.String-"
// Homepage links:
freemarkerdownload: "https://freemarker.apache.org/freemarkerdownload.html"
@@ -71,9 +74,10 @@
twitter: "https://twitter.com/freemarker"
sourceforgeProject: "https://sourceforge.net/projects/freemarker/"
githubProject: "https://github.com/freemarker/freemarker"
- newBugReport: "https://issues.apache.org/jira/browse/FREEMARKER/"
- newStackOverflowQuestion: "https://stackoverflow.com/questions/ask?tags=freemarker"
- asfIncubator: "http://incubator.apache.org/"
+ reportBug: "https://issues.apache.org/jira/browse/FREEMARKER/"
+ reportSecurityVulnerability: "https://freemarker.apache.org/report-security-vulnerabilities.html"
+ askHelpOnStackOverflow: "https://stackoverflow.com/questions/ask?tags=freemarker"
+ asfPrivacyPolicy: "https://privacy.apache.org/policies/privacy-policy-public.html"
}
internalBookmarks: {
@@ -99,7 +103,7 @@
// .icon-star
secondaryTabs: {
"Contribute": { class: "icon-heart", href: "olink:contribute" }
- "Report a Bug": { class: "icon-bug", href: "olink:newBugReport" }
+ "Report a Bug": { class: "icon-bug", href: "olink:reportBug" }
"Download": { class: "icon-download", href: "olink:freemarkerdownload" }
}
@@ -108,27 +112,30 @@
"What is FreeMarker?": "olink:what-is-freemarker"
"Download": "olink:freemarkerdownload"
"Version history": "id:app_versions"
- "About us": "olink:history"
+ "FAQ": "id:app_faq"
"License": "id:app_license"
+ "Privacy policy": "olink:asfPrivacyPolicy"
}
- "Handy stuff": {
+ "Often used / Reference": {
"Try template online": "olink:onlineTemplateTester"
"Expressions cheatsheet": "id:exp_cheatsheet"
"#directives": "id:ref_directive_alphaidx"
"?built_ins": "id:ref_builtins_alphaidx"
".special_vars": "id:ref_specvar"
+ "Configuration settings": "olink:apiConfigurationSettings"
}
"Community": {
- "FreeMarker on Github": "olink:githubProject"
- "Follow us on Twitter": "olink:twitter"
- "Report a bug": "olink:newBugReport"
- "Ask a question": "olink:newStackOverflowQuestion"
- "Mailing lists": "olink:mailing-lists"
+ "Github project page": "olink:githubProject"
+ "Report a bug": "olink:reportBug"
+ "Report security vulnerability": "olink:reportSecurityVulnerability"
+ "Get help on StackOverflow": "olink:askHelpOnStackOverflow"
+ "Announcements on Twitter": "olink:twitter"
+ "Discuss on mailing lists": "olink:mailing-lists"
}
}
socialLinks: {
"Github": { class: "github", href: "olink:githubProject" }
"Twitter": { class: "twitter", href: "olink:twitter" }
- "Stack Overflow": { class: "stack-overflow", href: "olink:newStackOverflowQuestion" }
+ "Stack Overflow": { class: "stack-overflow", href: "olink:askHelpOnStackOverflow" }
}
diff --git a/src/manual/zh_CN/book.xml b/src/manual/zh_CN/book.xml
deleted file mode 100644
index 81ef7fb..0000000
--- a/src/manual/zh_CN/book.xml
+++ /dev/null
@@ -1,31353 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
--->
-<book conformance="docgen" version="5.0" xml:lang="en"
- xmlns="http://docbook.org/ns/docbook"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:ns5="http://www.w3.org/1999/xhtml"
- xmlns:ns4="http://www.w3.org/2000/svg"
- xmlns:ns3="http://www.w3.org/1998/Math/MathML"
- xmlns:ns="http://docbook.org/ns/docbook">
- <info>
- <title>FreeMarker 手册</title>
-
- <titleabbrev>Manual</titleabbrev>
-
- <productname>Freemarker 2.3.23</productname>
- </info>
-
- <preface role="index.html" xml:id="preface">
- <title>什么是 FreeMarker?</title>
-
- <para>FreeMarker 是一款 <emphasis>模板引擎</emphasis>: 即一种基于模板和要改变的数据,
- 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。
- 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。</para>
-
- <para>模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言,
- <emphasis>不是</emphasis> 像PHP那样成熟的编程语言。
- 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,
- 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据,
- 而在模板之外可以专注于要展示什么数据。</para>
-
- <mediaobject>
- <imageobject>
- <imagedata fileref="figures/overview.png"/>
- </imageobject>
- </mediaobject>
-
- <para>这种方式通常被称为 <link
- linkend="gloss.MVC">MVC (模型 视图 控制器) 模式</link>,对于动态网页来说,是一种特别流行的模式。
- 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑,
- 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。</para>
-
- <para>而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到
- Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。</para>
-
- <para>FreeMarker 是 <link
- xlink:href="http://www.fsf.org/philosophy/free-sw.html">免费的</link>,
- 基于Apache许可证2.0版本发布。</para>
-
- <para>如果你发现 <emphasis>任何错误</emphasis> (包括
- <emphasis>语法错误</emphasis>, <emphasis>错别字</emphasis>,
- 排版错误) 或者是在文档中找到 <emphasis>误导或混淆</emphasis> ,或有其他建议,请联系原作者!
- Email: ddekany at users.sourceforge.net</para>
-
-
- <para>文档翻译的任何问题(包括语法错误,错别字)或中文技术交流,可以联系译者:nanlei1987 at gmail.com,
- 或在FreeMarker的Github上Fork一份,修改之后提交Pull Request。我们共同研究,共同进步。</para>
-
- <para>英文版文档的作者(也是FreeMarker项目的维护者)是匈牙利人,其母语非英语,那么在这种情况下,
- 翻译过程难免会有错误存在,译者结合自身多年对FreeMarker的实践力争精准,译文力求信达雅。
- 但因个人才疏学浅,水平有限,恳请广大读者批评指正。最好的方式就是使用Github了</para>
-
- <para>手册的更新根据原作者更新,大家的反馈随时进行。但只在有阶段性成果时才会提交发布修正版本。</para>
-
- <para>本翻译是免费的,您可以自由下载和传播,但不可用于任何商业行为。文档版权归译者本人所有,
- 原版归FreeMarker项目组所有,您可以引用其中的描述,但必须指明出处。如需用于商业行为,
- 您必须和原作者取得联系。</para>
- </preface>
-
- <part xml:id="dgui">
- <title>模板开发指南</title>
-
- <chapter xml:id="dgui_quickstart">
- <title>入门</title>
-
- <para>本章内容只是对 FreeMarker 进行简略的介绍,在后续章节中将会详细展开讲述。
- 不过没有关系,在阅读了本章节的内容后,就能够编写简单,但却很有用的FreeMarker模板程序了。</para>
-
- <section xml:id="dgui_quickstart_basics">
- <title>模板 + 数据模型 = 输出</title>
-
- <para>假设在一个在线商店的应用系统中需要一个HTML页面,和下面这个页面类似:</para>
-
- <programlisting role="output"><html>
-<head>
- <title>Welcome!</title>
-</head>
-<body>
- <h1>Welcome <emphasis>John Doe</emphasis>!</h1>
- <p>Our latest product:
- <a href="<emphasis>products/greenmouse.html</emphasis>"><emphasis>green mouse</emphasis></a>!
-</body>
-</html></programlisting>
-
- <para>这里的用户名(上面的"Big Joe"),应该是登录这个网页的访问者的名字,
- 并且最新产品的数据应该来自于数据库,这样它才能随时更新。那么不能直接在HTML页面中输入它们,
- 不能使用静态的HTML代码。此时,可以使用要求输出的 <emphasis role="term">模板</emphasis>。
- 模板和静态HTML是相同的,只是它会包含一些 FreeMarker 将它们变成动态内容的指令:</para>
-
- <programlisting role="template" xml:id="example.first"><html>
-<head>
- <title>Welcome!</title>
-</head>
-<body>
- <h1>Welcome <emphasis>${user}</emphasis>!</h1>
- <p>Our latest product:
- <a href="<emphasis>${latestProduct.url}</emphasis>"><emphasis>${latestProduct.name}</emphasis></a>!
-</body>
-</html></programlisting>
-
- <para>模板文件存放在Web服务器上,就像通常存放静态HTML页面那样。当有人来访问这个页面,
- FreeMarker将会介入执行,然后动态转换模板,用最新的数据内容替换模板中
- <literal>${<replaceable>...</replaceable>}</literal> 的部分,
- 之后将结果发送到访问者的Web浏览器中。访问者的Web浏览器就会接收到例如第一个HTML示例那样的内容
- (也就是没有FreeMarker指令的HTML代码),访问者也不会察觉到服务器端使用的FreeMarker。
- (当然,存储在Web服务器端的模板文件是不会被修改的;替换也仅仅出现在Web服务器的响应中。)</para>
-
- <para>请注意,模板并没有包含程序逻辑来查找当前的访问者是谁,或者去查询数据库获取最新的产品。
- 显示的数据是在 FreeMarker 之外准备的,通常是一些 <quote>真正的</quote> 编程语言(比如Java)
- 所编写的代码。模板作者无需知道这些值是如何计算出的。事实上,这些值的计算方式可以完全被修改,
- 而模板可以保持不变,而且页面的样式也可以完全被修改而无需改动模板。
- 当模板作者(设计师)和程序员不是同一人时,显示逻辑和业务逻辑相分离的做法是非常有用的,
- 即便模板作者和程序员是一个人,这么来做也会帮助管理应用程序的复杂性。
- 保证模板专注于显示问题(视觉设计,布局和格式化)是高效使用模板引擎的关键。</para>
-
- <para><indexterm>
- <primary>data-model</primary>
- </indexterm>为模板准备的数据整体被称作为 <emphasis role="term">数据模型</emphasis>。
- 模板作者要关心的是,数据模型是树形结构(就像硬盘上的文件夹和文件),在视觉效果上,
- 数据模型可以是:</para>
-
- <programlisting role="dataModel">(root)
- |
- +- <emphasis>user</emphasis> = "Big Joe"
- |
- +- <emphasis>latestProduct</emphasis>
- |
- +- <emphasis>url</emphasis> = "products/greenmouse.html"
- |
- +- <emphasis>name</emphasis> = "green mouse"</programlisting>
-
- <note>
- <para>上面只是一个形象化显示;数据模型不是文本格式,它来自于Java对象。
- 对于Java程序员来说,root就像一个有 <literal>getUser()</literal>
- 和 <literal>getLatestProduct()</literal> 方法的Java对象,
- 也可以有 <literal>"user"</literal> 和 <literal>"latestProducts"</literal>
- 键值的Java <literal>Map</literal>对象。相似地,<literal>latestProduct</literal>
- 就像是有 <literal>getUrl()</literal> 和 <literal>getName()</literal> 方法的Java对象。</para>
- </note>
-
- <para>早期版本中,可以从数据模型中选取这些值,使用 <literal>user</literal>
- 和 <literal>latestProduct.name</literal> 表达式即可。如果我们继续类推,
- 数据模型就像一个文件系统,那么 <quote>(root)</quote> 和 <literal>latestProduct</literal>
- 就对应着目录(文件夹),而 <literal>user</literal>, <literal>url</literal> 和
- <literal>name</literal> 就是这些目录中的文件。</para>
-
- <para>总的来说,模板和数据模型是FreeMarker来生成输出(比如第一个展示的HTML)所必须的:</para>
-
- <para><phrase role="markedTemplate">模板</phrase> + <phrase
- role="markedDataModel">数据模型</phrase> = <phrase
- role="markedOutput">输出</phrase></para>
- </section>
-
- <section xml:id="dgui_quickstart_datamodel">
- <title>数据模型一览</title>
-
- <para>正如已经看到的,数据模型的基本结构是树状的。
- 这棵树可以很复杂,并且可以有很大的深度,比如:</para>
-
- <programlisting role="dataModel"
- xml:id="example.qStart.dataModelWithHashes">(root)
- |
- +- animals
- | |
- | +- mouse
- | | |
- | | +- size = "small"
- | | |
- | | +- price = 50
- | |
- | +- elephant
- | | |
- | | +- size = "large"
- | | |
- | | +- price = 5000
- | |
- | +- python
- | |
- | +- size = "medium"
- | |
- | +- price = 4999
- |
- +- message = "It is a test"
- |
- +- misc
- |
- +- foo = "Something"</programlisting>
-
- <para>上图中的变量扮演目录的角色(比如 root,
- <literal>animals</literal>, <literal>mouse</literal>,
- <literal>elephant</literal>, <literal>python</literal>,
- <literal>misc</literal>) 被称为 <emphasis
- role="term">hashes</emphasis> (哈希表或哈希,译者注)。哈希表存储其他变量(被称为
- <anchor xml:id="topic.dataModel.subVar"/><emphasis>子变量</emphasis>),
- 它们可以通过名称来查找(比如 <quote>animals</quote>,
- <quote>mouse</quote> 或 <quote>price</quote>)。</para>
-
- <para>存储单值的变量
- (<literal>size</literal>, <literal>price</literal>,
- <literal>message</literal> 和 <literal>foo</literal>) 称为
- <emphasis role="term">scalars</emphasis> (标量,译者注)。</para>
-
- <para><anchor xml:id="topic.qStart.accessVariables"/>如果要在模板中使用子变量,
- 那应该从根root开始指定它的路径,每级之间用点来分隔开。要访问 <literal>mouse</literal> 的
- <literal>price</literal> ,要从root开始,首先进入到 <literal>animals</literal> ,之后访问
- <literal>mouse</literal> ,最后访问 <literal>price</literal> 。就可以这样来写
- <literal>animals.mouse.price</literal>。</para>
-
- <para>另外一种很重要的变量是 <emphasis role="term">sequences</emphasis> (序列,译者注)。
- 它们像哈希表那样存储子变量,但是子变量没有名字,它们只是列表中的项。
- 比如,在下面这个数据模型中, <literal>animals</literal> 和
- <literal>misc.fruits</literal> 就是序列:</para>
-
- <programlisting role="dataModel"
- xml:id="example.qStart.dataModelWithSequences">(root)
- |
- +- animals
- | |
- | +- (1st)
- | | |
- | | +- name = "mouse"
- | | |
- | | +- size = "small"
- | | |
- | | +- price = 50
- | |
- | +- (2nd)
- | | |
- | | +- name = "elephant"
- | | |
- | | +- size = "large"
- | | |
- | | +- price = 5000
- | |
- | +- (3rd)
- | |
- | +- name = "python"
- | |
- | +- size = "medium"
- | |
- | +- price = 4999
- |
- +- misc
- |
- +- fruits
- |
- +- (1st) = "orange"
- |
- +- (2nd) = "banana"</programlisting>
-
- <para>要访问序列的子变量,可以使用方括号形式的数字索引下标。
- 索引下标从0开始(从0开始也是程序员的传统),那么第一项的索引就是0,
- 第二项的索引就是1等等。要得到第一个动物的名称的话,可以这么来写代码
- <literal>animals[0].name</literal>。要得到 <literal>misc.fruits</literal>
- 中的第二项(字符串<literal>"banana"</literal>)可以这么来写
- <literal>misc.fruits[1]</literal>。(实践中,通常按顺序遍历序列,而不用关心索引,
- 这点会在 <link linkend="topic.tutorial.list">后续介绍</link>。)</para>
-
- <para>标量类型可以分为如下的类别:</para>
-
- <itemizedlist>
- <listitem>
- <para>字符串:就是文本,也就是任意的字符序列,比如上面提到的
- ''m'', ''o'', ''u'', ''s'', ''e''。比如 <literal>name</literal>
- 和 <literal>size</literal> 也是字符串。</para>
- </listitem>
-
- <listitem>
- <para>数字:这是数值类型,就像上面的 <literal>price</literal>。
- 在FreeMarker中,字符串 <literal>"50"</literal> 和数字
- <literal>50</literal> 是两种完全不同的东西。前者是两个字符的序列
- (这恰好是人们可以读的一个数字),而后者则是可以在数学运算中直接被使用的数值。</para>
- </listitem>
-
- <listitem>
- <para>日期/时间: 可以是日期-时间格式(存储某一天的日期和时间),
- 或者是日期(只有日期,没有时间),或者是时间(只有时间,没有日期)。</para>
- </listitem>
-
- <listitem>
- <para>布尔值:对应着对/错(是/否,开/关等值)类似的值。
- 比如动物可以有一个 <literal>protected</literal> (受保护的,译者注) 的子变量,
- 该变量存储这个动物是否被保护起来的值。</para>
- </listitem>
- </itemizedlist>
-
- <para>总结:</para>
-
- <itemizedlist>
- <listitem>
- <para>数据模型可以被看成是树形结构。</para>
- </listitem>
-
- <listitem>
- <para>标量用于存储单一的值。这种类型的值可以是字符串,数字,日期/时间或者是布尔值。</para>
- </listitem>
-
- <listitem>
- <para>哈希表是一种存储变量及其相关且有唯一标识名称的容器。</para>
- </listitem>
-
- <listitem>
- <para>序列是存储有序变量的容器。存储的变量可以通过数字索引来检索,索引通常从0开始。</para>
- </listitem>
- </itemizedlist>
-
- <note>
- <para>还有一些其它更为高级的类型,在这里我们并没有涉及到,比如方法和指令。</para>
- </note>
- </section>
-
- <section xml:id="dgui_quickstart_template">
- <title>模板一览</title>
-
- <para>最简单的模板通常是普通的HTML文件(或者是其他任何文本文件;
- FreeMarker本身不属于HTML)。当客户端访问某个页面时,
- FreeMarker要发送HTML代码至客户端浏览器中去显示。如果想要页面动起来
- (这里指动态网页技术,译者注),那么就要在HTML中放置能被FreeMarker所解析的特殊代码片段:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>${<replaceable>...</replaceable>}</literal>:
- FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 <emphasis
- role="term">interpolation</emphasis>(插值,译者注)。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">FTL 标签</emphasis> (FreeMarker模板的语言标签):
- FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。
- 这些标签的名字以 <literal>#</literal> 开头。(用户自定义的FTL标签则需要使用
- <literal>@</literal> 来代替 <literal>#</literal>,但这属于更高级的话题了。)</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">注释:</emphasis> 注释和HTML的注释也很相似,
- 但是它们使用 <literal><#--</literal> and <literal>--></literal> 来标识。
- 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中),
- 因为 FreeMarker会跳过它们。</para>
- </listitem>
- </itemizedlist>
-
- <para>其他任何不是FTL标签,插值或注释的内容将被视为静态文本,
- 这些东西不会被FreeMarker所解析;会被按照原样输出出来。</para>
-
- <para>FTL标签也被称为 <emphasis role="term">指令</emphasis>。
- 这些指令在HTML的标签 (比如: <literal><table></literal> 和
- <literal></table></literal>) 和HTML元素 (比如:
- <literal>table</literal> 元素) 中的关系是相同的。(如果现在还没有感觉到它们的不同,
- 那么把“FTL标签”和“指令”看做是同义词即可。)</para>
-
- <note>
- <para>可以在 <link
- xlink:href="http://freemarker-online.kenshoo.com/">http://freemarker-online.kenshoo.com/</link>
- 上很方便的尝试编写模板</para>
- </note>
-
- <section>
- <title>基本指令</title>
-
- <para>这里我们仅仅来看一些非常常用的指令,当然
- (<link linkend="ref_directives">指令还有很多</link>)。</para>
-
- <section>
- <title>if 指令</title>
-
- <para>使用 <literal>if</literal> 指令可以有条件地跳过模板的一些片段。
- 比如,假设在 <link linkend="example.first">最初的示例</link> 中,
- 想向你的老板Big Joe特别地问好,可其他人不同:</para>
-
- <programlisting role="template"><html>
-<head>
- <title>Welcome!</title>
-</head>
-<body>
- <h1>
- Welcome ${user}<emphasis><#if user == "Big Joe"></emphasis>, our beloved leader<emphasis></#if></emphasis>!
- </h1>
- <p>Our latest product:
- <a href="${latestProduct.url}">${latestProduct.name}</a>!
-</body>
-</html></programlisting>
-
- <para>此时,告诉 FreeMarker,当和 <literal>"Big Joe"</literal> 相同时
- <quote>, our beloved leader</quote> (我们最尊敬的领导,译者注)
- 才是if条件中那唯一的 <literal>user</literal> 变量的值。
- 通常来讲,如果 <literal><replaceable>condition</replaceable></literal>
- 是false(布尔值),那么介于 <literal><#if
- <replaceable>condition</replaceable>></literal> 和
- <literal></#if></literal> 标签中的内容会被略过。</para>
-
- <para>我们来详细说说
- <literal><replaceable>condition</replaceable></literal> 的使用:
- <literal>==</literal> 是用来判断它两侧的值是否相等的操作符,
- 比较的结果是布尔值,也就是true或者false。在 <literal>==</literal>
- 的左侧,是 <link linkend="topic.qStart.accessVariables">被引用的变量</link>,
- 我们很熟悉这样的语法结构;最终它会被变量的值所替代。通常来说,
- 在指令或插值中没有被引号标注的内容都被视为变量的引用。右侧则是指定的字符串,
- 在模板中的字符串 <emphasis>只能</emphasis> 放在引号内。</para>
-
- <para>当价格为0时,就会打印出 <quote>Pythons are free today!</quote>:</para>
-
- <programlisting role="template"><#if animals.python.price == <emphasis>0</emphasis>>
- Pythons are free today!
-</#if></programlisting>
-
- <para>和之前示例中,字符串被直接指定相似,
- 但这里则是数字(<literal>0</literal>)被直接指定了。
- 请注意,这里的数字 <emphasis>没有</emphasis> 放在引号内。
- 如果将(<literal>"0"</literal>)放在引号中,
- 那么FreeMarker就会将其误判为字符串了(也就是字符串0,译者注)。</para>
-
- <para>当价格不为0时,则会打印出"Pythons are not free today!":</para>
-
- <programlisting role="template"><#if animals.python.price <emphasis>!=</emphasis> 0>
- Pythons are not free today!
-</#if></programlisting>
-
- <para>你也许就会猜测了, <literal>!=</literal> 就是<quote>不等于</quote>。</para>
-
- <para>同时,也可以这样编来写代码(使用 <link
- linkend="example.qStart.dataModelWithHashes">数据模型来描述哈希表</link>):</para>
-
- <programlisting role="template"><#if <emphasis>animals.python.price < animals.elephant.price</emphasis>>
- Pythons are cheaper than elephants today.
-</#if></programlisting>
-
- <para>使用 <literal><#else></literal>
- 标签可以指定当条件为false时程序所要执行的内容。比如:</para>
-
- <programlisting role="template"><#if animals.python.price < animals.elephant.price>
- Pythons are cheaper than elephants today.
-<emphasis><#else></emphasis>
- Pythons are not cheaper than elephants today.
-</#if></programlisting>
-
- <para>这个示例中,如果蟒蛇的价格比大象的价格低的话,
- 程序将会打印出 <quote>Pythons are cheaper than elephants today.</quote>。
- 否则会打印 <quote>Pythons are not cheaper than elephants today.</quote>。
- 后面也可以使用 <literal>elseif</literal> 来完善它:</para>
-
- <programlisting role="template"><#if animals.python.price < animals.elephant.price>
- Pythons are cheaper than elephants today.
-<emphasis><#elseif animals.elephant.price < animals.python.price></emphasis>
- Elephants are cheaper than pythons today.
-<#else>
- Elephants and pythons cost the same today.
-</#if></programlisting>
-
- <para>如果变量本身就是布尔值(true/false),则可以直接让其作为
- <literal>if</literal> 的 <literal><replaceable>condition</replaceable></literal>
- (判断条件,译者注):</para>
-
- <programlisting role="template"><#if animals.python.protected>
- Pythons are protected animals!
-</#if></programlisting>
- </section>
-
- <section>
- <title>list 指令</title>
-
- <anchor xml:id="topic.tutorial.list"/>
-
- <para>当需要列表显示内容时,list指令是必须的。比如:
- 如果合并该模板到 <link
- linkend="example.qStart.dataModelWithSequences">前面描述序列的数据模型</link>
- 中:</para>
-
- <programlisting role="template"><p>We have these animals:
-<table border=1>
- <emphasis><#list animals as animal></emphasis>
- <tr><td>${<emphasis>animal</emphasis>.name}<td>${<emphasis>animal</emphasis>.price} Euros
- <emphasis></#list></emphasis>
-</table></programlisting>
-
- <para>那么输出结果将会是这样的:</para>
-
- <programlisting role="output"><p>We have these animals:
-<table border=1>
- <emphasis><tr><td>mouse<td>50 Euros
- <tr><td>elephant<td>5000 Euros
- <tr><td>python<td>4999 Euros</emphasis>
-</table></programlisting>
-
- <para> <literal>list</literal> 指令的一般格式为:
- <literal> <#list <replaceable>sequence</replaceable> as
- <replaceable>loopVariable</replaceable>><replaceable>repeatThis</replaceable></#list></literal>。
- <literal><replaceable>repeatThis</replaceable></literal> 部分将会在给定的
- <literal><replaceable>sequence</replaceable></literal> 遍历时在每一项中重复,
- 从第一项开始,一个接着一个。在所有的重复中,
- <literal><replaceable>loopVariable</replaceable></literal> 将持有当前遍历项的值。
- 这个变量仅存在于 <literal><#list
- <replaceable>...</replaceable>></literal> 和
- <literal></#list></literal> 标签内。</para>
-
- <para><literal><replaceable>sequence</replaceable></literal> 可以是任意表达式,
- 比如我们可以列表显示示例数据模型中的水果,就像这样:</para>
-
- <programlisting role="template"><ul>
-<emphasis><#list misc.fruits as fruit></emphasis>
- <li>${fruit}
-<emphasis></#list></emphasis>
-</ul></programlisting>
-
- <para>你应该很熟悉表达式 <literal>misc.fruits</literal> 了; 它 <link
- linkend="topic.qStart.accessVariables">引用了数据模型中的变量</link>。</para>
-
- <para>上面示例中的一个问题是如果我们有0个水果,它仍然会输出一个空的
- <literal><ul></ul></literal>,而不是什么都没有。
- 要避免这样的情况,可以这么来使用 <literal>list</literal>:</para>
-
- <programlisting role="template"><#list misc.fruits>
- <ul>
- <emphasis> <#items as fruit></emphasis>
- <li>${fruit}
- <emphasis> </#items></emphasis>
- </ul>
-</#list></programlisting>
-
- <para>此时, <literal>list</literal> 指令将列表视为一个整体,
- 在 <literal>items</literal> 指令中的部分才会为每个水果重复。
- 如果我们有0个水果,那么在 <literal>list</literal> 中的所有东西都被略过了,
- 因此就不会有 <literal>ul</literal> 标签了。</para>
-
- <para>另一个列表相关的常见任务是:使用一些分隔符来列出水果,比如逗号:</para>
-
- <programlisting role="template"><p>Fruits: <#list misc.fruits as fruit>${fruit}<emphasis><#sep>, </emphasis></#list></programlisting>
-
- <programlisting role="output"><p>Fruits: orange, banana</programlisting>
-
- <para>被 <literal>sep</literal> 覆盖的部分(我们也可以这么来写:
- <literal><replaceable>...</replaceable><#sep>,
- </#sep></#list></literal>) 只有当还有下一项时才会被执行。
- 因此最后一个水果后面不会有逗号。</para>
-
- <para>再次回到这个话题,如果我们有0个水果,会怎么样?只是打印
- <quote>Fruits:</quote> 也没有什么不方便。
- <literal>list</literal> 指令,也像 <literal>if</literal> 指令那样,可以有
- <literal>else</literal> 部分,如果列表中有0个元素时就会被执行:</para>
-
- <programlisting role="template"><p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, <emphasis><#else>None</emphasis></#list></programlisting>
-
- <note>
- <para>事实上,这个过于简单的示例可以这么来写,
- 但是它使用了本主题中没有介绍的语言特性:</para>
-
- <programlisting role="template"><p>Fruits: ${fruits?join(", ", "None")}</programlisting>
- </note>
-
- <para>所有的这些指令(<literal>list</literal>,
- <literal>items</literal>, <literal>sep</literal>,
- <literal>else</literal>)可以联合起来使用:</para>
-
- <programlisting role="template"><#list misc.fruits>
- <p>Fruits:
- <ul>
- <#items as fruit>
- <li>${fruit}<#sep> and</#sep>
- </#items>
- </ul>
-<#else>
- <p>We have no fruits.
-</#list></programlisting>
-
- <note>
- <para>在 <link linkend="ref_directive_list">指令参考</link> 中,
- 可以获取到更多关于这些指令的内容。</para>
- </note>
- </section>
-
- <section>
- <title>include 指令</title>
-
- <para>使用 <literal>include</literal> 指令,
- 我们可以在模板中插入其他文件的内容。</para>
-
- <para>假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含这些版权声明,
- 之后在需要它的地方插入即可。比方说,我们可以将版权信息单独存放在页面文件
- <literal>copyright_footer.html</literal> 中:</para>
-
- <programlisting role="template"><hr>
-<i>
-Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
-<br>
-All Rights Reserved.
-</i></programlisting>
-
- <para>当需要用到这个文件时,可以使用 <literal>include</literal> 指令来插入:</para>
-
- <programlisting role="template"><html>
-<head>
- <title>Test page</title>
-</head>
-<body>
- <h1>Test page</h1>
- <p>Blah blah...
-<emphasis> <#include "/copyright_footer.html"></emphasis>
-</body>
-</html></programlisting>
-
- <para>此时,输出的内容为:</para>
-
- <programlisting role="output"><html>
-<head>
- <title>Test page</title>
-</head>
-<body>
- <h1>Test page</h1>
- <p>Blah blah...
-<emphasis><hr>
-<i>
-Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
-<br>
-All Rights Reserved.
-</i></emphasis>
-</body>
-</html></programlisting>
-
- <para>当修改了 <literal>copyright_footer.html</literal> 文件,
- 那么访问者在所有页面都会看到版权声明的新内容。</para>
-
- <note>
- <para>重用代码片段的一个更有力的方式是使用宏,但是只是更为高级的话题了,
- 将会在 <link linkend="dgui_misc_userdefdir">后续讨论</link>。</para>
- </note>
- </section>
- </section>
-
- <section>
- <title>联合使用指令</title>
-
- <para>在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。
- 比如,在 <literal>list</literal> 指令中嵌套 <literal>if</literal> 指令:</para>
-
- <programlisting role="template"><emphasis><#list animals as animal></emphasis>
- <div<emphasis><#if animal.protected></emphasis><emphasis> </emphasis>class="protected"<emphasis></#if></emphasis>>
- ${animal.name} for ${animal.price} Euros
- </div>
-<emphasis></#list></emphasis></programlisting>
-
- <para>请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释,
- 上面示例在HTML属性中使用FTL标签也不会有问题。</para>
- </section>
-
- <section>
- <title>使用内建函数</title>
-
- <para>内建函数很像子变量(如果了解Java术语的话,也可以说像方法),
- 它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。
- 为了清晰子变量是哪部分,使用 <literal>?</literal>(问号)代替
- <literal>.</literal>(点)来访问它们。<anchor
- xml:id="topic.commonlyUsedBuiltIns"/>常用内建函数的示例:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>user?html</literal> 给出 <literal>user</literal> 的HTML转义版本,
- 比如 <literal>&</literal> 会由 <literal>&amp;</literal> 来代替。</para>
- </listitem>
-
- <listitem>
- <para><literal>user?upper_case</literal> 给出 <literal>user</literal> 值的大写版本
- (比如 <quote>JOHN DOE</quote> 来替代 <quote>John
- Doe</quote>)</para>
- </listitem>
-
- <listitem>
- <para><literal>animal.name?cap_first</literal> 给出 <literal>animal.name</literal>
- 的首字母大写版本(比如 <quote>Mouse</quote> 来替代 <quote>mouse</quote>)</para>
- </listitem>
-
- <listitem>
- <para><literal>user?length</literal> 给出 <literal>user</literal> 值中
- <emphasis>字符</emphasis>的数量(对于 <quote>John Doe</quote> 来说就是8)</para>
- </listitem>
-
- <listitem>
- <para><literal>animals?size</literal> 给出 <literal>animals</literal> 序列中
- <emphasis>项目</emphasis> 的个数(我们示例数据模型中是3个)</para>
- </listitem>
-
- <listitem>
- <para>如果在 <literal><#list animals as animal></literal> 和对应的
- <literal></#list></literal> 标签中:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>animal?index</literal> 给出了在 <literal>animals</literal>
- 中基于0开始的 <literal>animal</literal>的索引值</para>
- </listitem>
-
- <listitem>
- <para><literal>animal?counter</literal> 也像 <literal>index</literal>,
- 但是给出的是基于1的索引值</para>
- </listitem>
-
- <listitem>
- <para><literal>animal?item_parity</literal> 基于当前计数的奇偶性,给出字符串
- <quote>odd</quote> 或 <quote>even</quote>。在给不同行着色时非常有用,比如在
- <literal><td class="${animal?item_parity}Row"></literal>中。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <para>一些内建函数需要参数来指定行为,比如:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>animal.protected?string("Y", "N")</literal> 基于
- <literal>animal.protected</literal> 的布尔值来返回字符串
- <quote>Y</quote> 或 <quote>N</quote>。</para>
- </listitem>
-
- <listitem>
- <para><literal>animal?item_cycle('lightRow','darkRow')</literal> 是之前介绍的
- <literal>item_parity</literal> 更为常用的变体形式。</para>
- </listitem>
-
- <listitem>
- <para><literal>fruits?join(", ")</literal> 通过连接所有项,将列表转换为字符串,
- 在每个项之间插入参数分隔符(比如 <quote>orange,banana</quote>)</para>
- </listitem>
-
- <listitem>
- <para><literal>user?starts_with("J")</literal> 根据 <literal>user</literal>
- 的首字母是否是 <quote>J</quote> 返回布尔值true或false。</para>
- </listitem>
- </itemizedlist>
-
- <para>内建函数应用可以链式操作,比如<literal>user?upper_case?html</literal>
- 会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用
- <literal>.</literal>(点)一样)</para>
-
- <para>可以阅读 <link linkend="ref_builtins">全部内建函数参考</link>。</para>
- </section>
-
- <section>
- <title>处理不存在的变量</title>
-
- <para>数据模型中经常会有可选的变量(也就是说有时并不存在)。
- 除了一些典型的人为原因导致失误外,FreeMarker 绝不能容忍引用不存在的变量,
- 除非明确地告诉它当变量不存在时如何处理。这里来介绍两种典型的处理方法。</para>
-
- <para><phrase role="forProgrammers">这部分对程序员而言:
- 一个不存在的变量和一个是 <literal>null</literal> 值的变量,
- 对于FreeMarker来说是一样的,所以这里所指的"丢失"包含这两种情况。</phrase></para>
-
- <para>不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况,
- 通过在变量名后面跟着一个 <literal>!</literal>(叹号,译者注)和默认值。
- 就像下面的这个例子,当 <literal>user</literal> 不存在于数据模型时,
- 模板将会将 <literal>user</literal> 的值表示为字符串
- <literal>"visitor"</literal>。(当 <literal>user</literal> 存在时,
- 模板就会表现出 <literal>${user}</literal> 的值):</para>
-
- <programlisting role="template"><h1>Welcome ${user<emphasis>!"visitor"</emphasis>}!</h1></programlisting>
-
- <para>也可以在变量名后面通过放置 <literal>??</literal>
- 来询问一个变量是否存在。将它和 <literal>if</literal> 指令合并,
- 那么如果 <literal>user</literal> 变量不存在的话将会忽略整个问候的代码段:</para>
-
- <programlisting role="template"><#if <emphasis>user??</emphasis>><h1>Welcome ${user}!</h1></#if></programlisting>
-
- <para>关于多级访问的变量,比如 <literal>animals.python.price</literal>,
- 书写代码:<literal>animals.python.price!0</literal>
- 当且仅当 <literal>animals.python</literal> 永远存在,
- 而仅仅最后一个子变量 <literal>price</literal> 可能不存在时是正确的
- (这种情况下我们假设价格是 <literal>0</literal>)。
- 如果 <literal>animals</literal> 或 <literal>python</literal> 不存在,
- 那么模板处理过程将会以"未定义的变量"错误而停止。为了防止这种情况的发生,
- 可以如下这样来编写代码 <literal>(animals.python.price)!0</literal>。
- 这种情况就是说 <literal>animals</literal> 或 <literal>python</literal> 不存在时,
- 表达式的结果是 <literal>0</literal>。对于 <literal>??</literal>
- 也是同样用来的处理这种逻辑的; 将 <literal>animals.python.price??</literal> 对比
- <literal>(animals.python.price)??</literal>来看。</para>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="dgui_datamodel">
- <title>数值,类型</title>
-
- <section xml:id="dgui_datamodel_basics">
- <title>基本内容</title>
-
- <note>
- <para>这里假设你已经阅读完 <xref linkend="dgui_quickstart"/> 章节的内容了。</para>
- </note>
-
- <para>理解数值和类型的概念是理解数据模型的关键和基础。
- 但数值和类型的概念并不局限于数据模型,下面你就会看到了。</para>
-
- <section xml:id="topic.value">
- <title>什么是数值?</title>
-
- <indexterm>
- <primary>value</primary>
- </indexterm>
-
- <para><phrase role="forProgrammers">这部分对于程序员来说可以直接跳过的。</phrase></para>
-
- <para>正如你知道的,来自于每天所使用的数字,比如16,0.5等这些用语就是
- <emphasis>数值</emphasis> 的示例,也就是数字。在计算机语言中,
- 这些用语有着更广泛的含义,比如数值并不一定是数字类型值,比如面这个数据模型:</para>
-
- <programlisting role="dataModel" xml:id="example.stdDataModel">(root)
- |
- +- user = "Big Joe"
- |
- +- today = Jul 6, 2007
- |
- +- todayHoliday = false
- |
- +- lotteryNumbers
- | |
- | +- (1st) = 20
- | |
- | +- (2st) = 14
- | |
- | +- (3rd) = 42
- | |
- | +- (4th) = 8
- | |
- | +- (5th) = 15
- |
- +- cargo
- |
- +- name = "coal"
- |
- +- weight = 40
-</programlisting>
-
- <para>变量 <literal>user</literal> 的 <emphasis>value</emphasis>
- 是"Big Joe"(字符串), <literal>today</literal> 的 <emphasis>value</emphasis>
- 是 Jul 6, 2007 (日期),<literal>todayHoliday</literal> 的
- <emphasis>value</emphasis> 是false(布尔值,比如yes/no等值)。
- <literal>lotteryNumbers</literal> 的 <emphasis>value</emphasis>
- 是包含20,14, 42, 8, 15的序列。当然在这种意义上,
- <literal>lotteryNumbers</literal> 是多值类型。它 <emphasis>包含</emphasis>
- 多个值(比如,其中的第二项的 <emphasis>value</emphasis> 是14),但是
- <literal>lotteryNumbers</literal> 本身还是单值。它就像一个装有其它很多东西的盒子
- (或称之为容器,译者注),但盒子作为整体还是视作单独的。最后还有一个数值
- <literal>cargo</literal>,它的 <emphasis>value</emphasis> 是一个哈希表
- (也是类似盒子一样的东西)。所以说,数值就是存储在变量中的(比如,在
- <literal>user</literal> 或 <literal>cargo</literal> 或
- <literal>cargo.name</literal> 中)的那个东西。但是,
- 不需要存储于变量之中的数值也可以称之为数值,比如下面的数字100:</para>
-
- <programlisting role="template"><#if cargo.weight < <emphasis>100</emphasis>>Light cargo</#if></programlisting>
-
- <para>当模板被执行时,计算出的临时结果也称为数值,比如这里的20和120(它会打印120):</para>
-
- <programlisting role="template">${cargo.weight / 2 + 100}</programlisting>
-
- <para>这里针对最后一种表示进行解释:有两个数,40(货物的重量)和2,
- 相除的结果是20,这是一个新计算出来的数值。然后,把它和100相加,
- 那么120就计算出来了,接着就打印出来。
- (<literal>${<replaceable>...</replaceable>}</literal>),之后模板继续向下执行,
- 直到所有结果都计算出来。</para>
-
- <para>现在你应该能体会到数值这个词的含义了。</para>
- </section>
-
- <section>
- <title>什么是类型?</title>
-
- <para>数值中非常重要的一个概念就是类型。比方说,变量
- <literal>user</literal> 的类型是字符串,变量 <literal>lotteryNumbers</literal>
- 的类型是序列。数值的类型这个概念非常的重要,因为它决定了这些数值可以在哪里使用的最大限度。
- 比如说,使用 <literal>${user / 2}</literal> 就是错误的,但是使用
- <literal>${cargo.weight / 2}</literal> 就能计算出结果,为20,
- 因为算术中的除法仅对数字类型的值有效,而不能用于字符串。
- 仅当 <literal>cargo</literal> 是一个哈希表变量时,表达式 <literal>cargo.name</literal>
- 才可以使用点。也可以用 <literal><#list <replaceable>...</replaceable>></literal>
- 指令来遍历序列。而 <literal><#if ...></literal> 指令的条件只能是布尔值等。</para>
-
- <note>
- <para>这里说一点点的术语:称 "布尔" 或 "布尔值" 或 "布尔类型" 都是相同的含义。</para>
- </note>
-
- <para xml:id="topic.multitype"><indexterm>
- <primary>Multi-typed value</primary>
- </indexterm>数值同时也可以含有多种类型,尽管很少这么使用。看一下下面这个数据模型
- <literal>mouse</literal> 它本身就又是字符串,又是哈希表:</para>
-
- <programlisting role="dataModel">(root)
- |
- +- mouse = "Yerri"
- |
- +- age = 12
- |
- +- color = "brown"</programlisting>
-
- <para>如果用上面的数据模型合并到下面的模板中:</para>
-
- <programlisting role="template">${mouse} <#-- uses mouse as a string -->
-${mouse.age} <#-- uses mouse as a hash -->
-${mouse.color} <#-- uses mouse as a hash --></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Yerri
-12
-brown</programlisting>
- </section>
-
- <section>
- <title>数据模型是哈希表</title>
-
- <para>注意观察每个数据模型的例子你也许能发现:被"(root)"所标识的内容就是哈希表类型的值。
- 当编写如 <literal>user</literal> 这样的代码时,那就意味着要把"user"变量存储在哈希表的根上。
- 就像编写 <literal>root.user</literal>一样,这里但并没有名"root"为的变量,
- 那么这就起不到任何作用了。</para>
-
- <para>某些人也许会被这种数据模型的例子所困惑,也就是说,根哈希表包含更多的哈希表或序列
- (<literal>lotteryNumbers</literal> and <literal>cargo</literal>)。其它就没有更特殊的内容了。
- 哈希表包含其他变量,那些变量包含其它值,这些数值可以是字符串,数字等变量,
- 当然也可以是哈希表或序列变量。最初我们解释过的,就像字符串和数字,
- 序列或哈希表也是一种值的表示形式。</para>
- </section>
- </section>
-
- <section xml:id="dgui_datamodel_types">
- <title>类型</title>
-
- <para>支持的类型有:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="dgui_datamodel_scalar"
- os="">标量:</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>字符串</para>
- </listitem>
-
- <listitem>
- <para>数字</para>
- </listitem>
-
- <listitem>
- <para>布尔值</para>
- </listitem>
-
- <listitem>
- <para>日期/时间 (日期,时间或日期时间)</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_datamodel_container">容器:</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>哈希表</para>
- </listitem>
-
- <listitem>
- <para>序列</para>
- </listitem>
-
- <listitem>
- <para>集合</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>子程序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="dgui_datamodel_method">方法和函数</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_datamodel_userdefdir">用户自定义指令</link></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>其它/很少使用:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="dgui_datamodel_node">结点</link></para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <section xml:id="dgui_datamodel_scalar">
- <title>标量</title>
-
- <anchor xml:id="topic.designer.scalarVariable"/>
-
- <para>标量是最基本,最简单的数值类型,它们可以是:</para>
-
- <itemizedlist>
- <listitem>
- <para><indexterm>
- <primary>string</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>字符串:表示简单的文本,例如:产品的名称。</para>
-
- <para>如果想在模板中直接给出字符串值,而不是使用数据模型中的变量,
- 那么将文本内容写在引号内即可,比如 <literal>"green mouse"</literal>
- 或 <literal>'green mouse'</literal>。(关于语法的更多细节可以在
- <link linkend="dgui_template_exp_direct_string" xml:lang="">后续章节</link>中找到。)</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>number</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>数值:比如说,产品的价格。
- <phrase role="forProgrammers">整数和非整数是不区分的;只有单一的数字类型。比如使用了计算器,
- 计算3/2的结果是1.5而不是1。</phrase></para>
-
- <para>如果要在模板中直接给出数字的值,那么可以这么来写:
- <literal>150</literal> 或 <literal>-90.05</literal>
- 或 <literal>0.001</literal>。(关于语法的更多细节可以在 <link
- linkend="dgui_template_exp_direct_number" xml:lang="">后续章节</link>中找到。)</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>boolean</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>布尔值:布尔值代表了逻辑上的对或错(是或否)。比如:用户是否登录了。
- 典型的应用是使用布尔值作为 <literal>if</literal> 指令的条件,
- 比如 <literal><#if loggedIn ><replaceable>...</replaceable></#if></literal> 或者
- <literal><#if price == 0><replaceable>...</replaceable></#if></literal>;
- 后面这个 <literal>price == 0</literal> 部分的结果就是布尔值。</para>
-
- <para>在模板中可以使用保留字 <literal>true</literal>
- 和 <literal>false</literal> 来指定布尔值。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>date</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm><indexterm>
- <primary>time</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm><indexterm>
- <primary>date-time</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>日期:日期变量可以存储和日期/时间相关的数据。
- 一共有三种变化:</para>
-
- <itemizedlist>
- <listitem>
- <para>日期:精确到天的日期,没有时间部分。比如April 4, 2003。</para>
- </listitem>
-
- <listitem>
- <para>时间:精确到毫秒,没有日期部分。比如10:19:18 PM。</para>
- </listitem>
-
- <listitem>
- <para>日期-时间(有时也被称为"时间戳"),比如April 4,2003 10:19:18 PM。
- 有日期和时间两部分,时间部分的存储精确到毫秒。</para>
- </listitem>
- </itemizedlist>
-
- <para>不幸的是,受到Java平台的限制,FreeMarker 有时是不能决定日期的部哪分被使用
- (也就是说,是日期-时间格式,日期格式还是时间格式)。
- 这个问题的解决方法是一个的高级话题,将会在
- <link linkend="ref_builtin_date_datetype">后续章节</link>讨论。</para>
-
- <para>模板中直接定义日期数值是可以的,但这也是高级话题,将会在
- <link linkend="ref_builtin_string_date">后续章节</link> 中进行解释。</para>
- </listitem>
- </itemizedlist>
-
- <para>要记住,FreeMarker区别字符串,数字,布尔值和日期类型的值。比如,
- 字符串 <literal>"150"</literal> 看起来很像数字 <literal>150</literal>,
- 字符串只是字符的任意序列,不能将它用于计算目的,也不能和其它数字进行比较等等。</para>
- </section>
-
- <section xml:id="dgui_datamodel_container">
- <title>容器</title>
-
- <remark>站在一个比之前更''专业''的视角上来重新解释哈希表和序列,
- 还有一些深入的思考。</remark>
-
- <para>这些值存在的目的是为了包含其他变量;它们只是容器。
- 它们包含的变量通常视为 <emphasis>subvariables</emphasis>
- (子变量,译者注)。容器的类型有:</para>
-
- <itemizedlist>
- <listitem>
- <para><indexterm>
- <primary>hash</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>哈希表:每个子变量都可以通过一个唯一的名称来查找。
- 这个名称是不受限制的字符串。哈希表 <emphasis>并不确定其中子变量的顺序</emphasis>。
- 也就是说没有第一个子变量,第二个子变量这样的说法等;变量仅仅是通过名称来访问的。
- (就像Java语言中的HashMap一样,是实现了Hash算法的Map,不记录内部元素的顺序,
- 仅仅通过名称来访问。译者注)</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>sequence</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>序列:每个子变量通过一个整数来标识。第一个子变量的标识符是0,
- 第二个是1,第三个是2,这样来类推,而且子变量是有顺序的。这些数次通常被称为
- <emphasis>indexes</emphasis>(索引,译者注)。序列通常比较密集,也就是所有的索引,
- 包括最后一个子变量的,它们和子变量都是相关联的,但不是绝对必要的。
- 子变量的类型也并不需要完全一致。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>collection</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>集合:从模板设计者角度来看,集合是有限制的序列。不能获取集合的大小,
- 也不能通过索引取出集合中的子变量,但是它们仍然可以通过
- <link linkend="ref.directive.list"><literal>list</literal> 指令</link>来遍历。</para>
- </listitem>
- </itemizedlist>
-
- <para>请注意,一个值也可有多种类型
- <link linkend="topic.multitype">一个值也可有多种类型</link>,
- 对于一个值可能同时存在哈希表和序列这两种类型,这时,
- 该变量就支持索引和名称两种访问方式。
- 不过容器基本是当作哈希表或者序列来使用的,而不是两者同时使用。</para>
-
- <para>尽管存储在哈希表,序列(集合)中的变量可以是任意类型的,
- 这些变量也可以是哈希表,序列(或集合)。这样就可以构建任意深度的数据结构。</para>
-
- <para>数据模型本身(最好说成是它的根root)也是哈希表。</para>
- </section>
-
- <section>
- <title>子程序</title>
-
- <section xml:id="dgui_datamodel_method">
- <title>方法和函数</title>
-
- <anchor xml:id="topic.designer.methodVariable"/>
-
- <indexterm>
- <primary>method</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>
-
- <para>当一个值是方法或函数的时候,那么它就可以计算其他值,结果取决于传递给它的参数。</para>
-
- <para><phrase role="forProgrammers">这部分是对程序员来说的:方法/函数是一等类型值,
- 就像函数化的编程语言。也就是说函数/方法也可以是其他函数/方法的参数或者返回值,
- 并可以把它们定义成变量等。</phrase></para>
-
- <para>假设程序员在数据模型中放置了一个方法变量 <literal>avg</literal>,
- 该变量用来计算数字的平均值。如果给定3和5作为参数,访问
- <literal>avg</literal> 时就能得到结果4。</para>
-
- <para>方法的使用将会在 <link
- linkend="dgui_template_exp_methodcall">后续章节</link> 中进行解释,
- 下面这个示例会帮助我们理解方法的使用:</para>
-
- <programlisting role="template">The average of 3 and 5 is: ${avg(3, 5)}
-The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
-The average of the price of a python and an elephant is:
-${avg(animals.python.price, animals.elephant.price)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">The average of 3 and 5 is: 4
-The average of 6 and 10 and 20 is: 12
-The average of the price of a python and an elephant is:
-4999.5</programlisting>
-
- <para>那么方法和函数有什么区别呢?这是模板作者所关心的,
- 它们没有关系,但也不是一点关系都没有。
- 方法是来自于数据模型 (<phrase
- role="forProgrammers">它们反射了Java对象的方法</phrase>)
- 而函数是定义在模板内的 (使用 <link
- linkend="ref.directive.function"><literal>function</literal>
- 指令</link> -- 也是高级话题),但二者可以用同一种方式来使用。</para>
- </section>
-
- <section xml:id="dgui_datamodel_userdefdir">
- <title>用户自定义指令</title>
-
- <indexterm>
- <primary>macro</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>
-
- <indexterm>
- <primary>directive</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>
-
- <indexterm>
- <primary>user-defined directive</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>
-
- <para>这种类型的值可以作为用户自定义指令(换句话说,就是FreeMarker的标签)
- 用户自定义指令是一种子程序,一种可以复用的模板代码段。但这也是一个高级话题,
- 将会在 <link linkend="dgui_misc_userdefdir">后续章节</link> 中进行解释。</para>
-
- <para><phrase role="forProgrammers">这部分是对程序员来说的:
- 用户自定义指令(比如宏)也是一等值类型,就像函数/方法一样。</phrase></para>
-
- <para>这里仅仅对用户自定义指令有一个认识即可(如果现在还不能理解可以先忽略它)。
- 假设现在有一个变量 <literal>box</literal>,它的值是用户自定义的指令,
- 用来打印一些特定的HTML信息,包含标题和一条信息。那么,
- <literal>box</literal> 变量就可以在模板中使用(示例如下):</para>
-
- <programlisting role="template"><@<emphasis>box</emphasis> title="Attention!">
- Too much copy-pasting may leads to
- maintenance headaches.
-</@<emphasis>box</emphasis>></programlisting>
- </section>
-
- <section>
- <title>函数/方法和用户自定义指令的比较</title>
-
- <para>这部分内容也是对高级用户来说的(如果还不能理解可以先忽略它)。
- 如果要使用函数/方法或自定义指令去实现一些东西的时候,
- 二者之间的选择是两难的。按经验来说,如果能够实现需求,
- 请先用自定义指令而不要用函数/方法。如果:</para>
-
- <itemizedlist>
- <listitem>
- <para>... 输出(返回值)的是标记(HTML,XML等)。
- 主要原因是函数的返回结果可以自动进行XML转义(这是因为
- <literal>${<replaceable>...</replaceable>}</literal> 的特性),
- 而用户自定义指令的输出则不是
- (这是因为 <literal><@<replaceable>...</replaceable>></literal> 的特性所致;
- 它的输出假定是标记,因此已经转义过了)。</para>
- </listitem>
-
- <listitem>
- <para>... 副作用也是很重要的一点,它没有返回值。
- 例如一个指令的目的是往服务器日志中添加一条。
- (事实上不能得到自定义指令的返回值,
- 但有些反馈的类型是有可能设置非本地变量的。)</para>
- </listitem>
-
- <listitem>
- <para>... 会进行流程的控制(就像 <literal>list</literal>
- 或 <literal>if</literal> 指令那样)。但是不能在函数/方法上这么做。</para>
- </listitem>
- </itemizedlist>
-
- <para>在模板中,FreeMarker不知道的Java对象的方法通常是可以作为方法来使用的,
- 而不用考虑Java对象方法本身的特性,因为在这里没有其他的选择。</para>
- </section>
- </section>
-
- <section>
- <title>其它</title>
-
- <section xml:id="dgui_datamodel_node">
- <title>结点</title>
-
- <indexterm>
- <primary>node</primary>
-
- <secondary>FTL 值类型</secondary>
- </indexterm>
-
- <para>结点变量代表了树状结构中的一个结点,而且通常是配合
- <link linkend="xgui">XML 处理</link>的,这是专业而且更高级的话题。</para>
-
- <para>这里我们仅对 <emphasis>高级用户</emphasis> 进行一个概要说明:
- 结点和存储在其他结点中的序列很相似,通常也被当作为子结点。结点存储它所在的容器结点的引用,
- 也就是父结点。结点的主要作用是拓扑信息;其它数据必须通过使用多类型的值来存储。
- 就像一个值可以同时是一个结点和一个数字,这样它存储的数字可以作为如支付额来使用。
- 除了拓扑信息,结点也可以存储一些元信息(即metadata,译者注):如结点名称,它的类型(字符串),
- 命名空间(字符串)。若一个结点象征XHTML文档中的 <literal>h1</literal> 元素,
- 那么它的名字可以是 <literal>"h1"</literal>,类型可以是 <literal>"element"</literal>,
- 命名空间可以是 <literal>"http://www.w3.org/1999/xhtml"</literal>。但对于数据模型设计者来说,
- 这些元信息,还有如何来使用它们又有什么意义呢。检索拓扑信息和元信息的方法将会在
- <link linkend="ref_builtins_node">后续章节</link> 中来说明(这里可以先不用理解它们)。</para>
- </section>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="dgui_template">
- <title>模板</title>
-
- <indexterm>
- <primary>template</primary>
- </indexterm>
-
- <note>
- <para>这里假设你已经阅读完 <xref linkend="dgui_quickstart"/>
- 和 <xref linkend="dgui_datamodel"/> 章节了。</para>
- </note>
-
- <section xml:id="dgui_template_overallstructure">
- <title>总体结构</title>
-
- <para>实际上用程序语言编写的程序就是模板。
- <indexterm>
- <primary>FTL</primary>
- </indexterm><emphasis role="term">FTL</emphasis> (代表FreeMarker模板语言)。
- 这是为编写模板设计的非常简单的编程语言。</para>
-
- <para>模板(FTL编程)是由如下部分混合而成的:</para>
-
- <itemizedlist>
- <listitem>
- <para><emphasis role="term">文本</emphasis><indexterm>
- <primary>text</primary>
- </indexterm>:文本会照着原样来输出。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">插值</emphasis><indexterm>
- <primary>interpolation</primary>
- </indexterm>:这部分的输出会被计算的值来替换。插值由
- <literal>${</literal> and <literal>}</literal> 所分隔(或者
- <literal>#{</literal> and <literal>}</literal>,这种风格已经不建议再使用了;<link
- linkend="ref_depr_numerical_interpolation">点击查看更多</link>)。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">FTL 标签</emphasis><indexterm>
- <primary>FTL tag</primary>
- </indexterm>:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示,
- 而且不会打印在输出内容中。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">注释</emphasis><indexterm>
- <primary>comment</primary>
- </indexterm><indexterm>
- <primary><#--...--></primary>
- </indexterm><indexterm>
- <primary>#</primary>
- </indexterm>:注释和HTML的注释也很相似,但它们是由
- <literal><#--</literal> 和
- <literal>--></literal>来分隔的。注释会被FreeMarker直接忽略,
- 更不会在输出内容中显示。</para>
- </listitem>
- </itemizedlist>
-
- <para>我们来看一个具体的模板。其中的内容已经用颜色来标记了:
- <phrase role="markedText">文本</phrase>,
- <phrase role="markedInterpolation">插值</phrase>, <phrase
- role="markedFTLTag">FTL 标签</phrase>, <phrase
- role="markedComment">注释</phrase>。为了看到可见的
- <link linkend="gloss.lineBreak">换行符</link>,
- 这里使用了 <phrase role="markedInvisibleText">[BR]</phrase>。</para>
-
- <programlisting role="template"><phrase role="markedText"><html><phrase
- role="markedInvisibleText">[BR]</phrase>
-<head><phrase role="markedInvisibleText">[BR]</phrase>
- <title>Welcome!</title><phrase role="markedInvisibleText">[BR]</phrase>
-</head><phrase role="markedInvisibleText">[BR]</phrase>
-<body><phrase role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedComment"><#-- Greet the user with his/her name --></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <h1>Welcome <phrase role="markedInterpolation">${user}</phrase>!</h1><phrase
- role="markedInvisibleText">[BR]</phrase>
- <p>We have these animals:<phrase role="markedInvisibleText">[BR]</phrase>
- <ul><phrase role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedFTLTag"><#list animals as animal></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <li><phrase role="markedInterpolation">${animal.name}</phrase> for <phrase
- role="markedInterpolation">${animal.price}</phrase> Euros<phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedFTLTag"></#list></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- </ul><phrase role="markedInvisibleText">[BR]</phrase>
-</body><phrase role="markedInvisibleText">[BR]</phrase>
-</html></phrase></programlisting>
-
- <para>FTL是区分大小写的。 <literal>list</literal> 是指令的名称而
- <literal>List</literal> 就不是。类似地 <literal>${name}</literal>
- 和 <literal>${Name}</literal> 或 <literal>${NAME}</literal> 也是不同的。</para>
-
- <para>请注意非常重要的一点: <phrase role="markedInterpolation">插值</phrase>
- 仅仅可以在 <phrase role="markedText">文本</phrase> 中使用。
- (也可以是字符串表达式;请参考
- <link linkend="dgui_template_exp_stringop_interpolation">后续内容</link>)</para>
-
- <para><phrase role="markedFTLTag">FTL 标签</phrase> 不可以在其他
- <phrase role="markedFTLTag">FTL 标签</phrase>
- 和 <phrase role="markedInterpolation">插值</phrase>中使用。比如,
- 这样做是 <emphasis>错误</emphasis> 的:
- <literal><#if <#include 'foo'>='bar'>...</#if></literal></para>
-
- <para><phrase role="markedComment">注释</phrase> 可以放在
- <phrase role="markedFTLTag">FTL 标签</phrase>
- 和 <phrase role="markedInterpolation">插值</phrase>中。比如:</para>
-
- <programlisting role="template"><phrase role="markedText"><h1>Welcome <phrase
- role="markedInterpolation">${user <phrase role="markedComment"><#-- The name of user --></phrase>}</phrase>!</h1><phrase
- role="markedInvisibleText">[BR]</phrase>
-<p>We have these animals:<phrase role="markedInvisibleText">[BR]</phrase>
-<ul><phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"><#list <phrase role="markedComment"><#-- some comment... --></phrase> animals as <phrase
- role="markedComment"><#-- again... --></phrase> animal></phrase><phrase
- role="markedInvisibleText">[BR]</phrase></phrase>
-<replaceable>...</replaceable></programlisting>
-
- <note>
- <para>如果您已经自行尝试测试上面所有示例的话:那么也许会注意到,一些空格、
- 制表符和换行符从模板的输出中都不见了,尽管我们之前已经说了
- <phrase role="markedText">文本</phrase> 是按照原样输出的。
- 现在不用为此而计较,这是由于FreeMarker的"空格剥离"特性在起作用,
- 它当然会自动去除一些多余的空格,制表符和换行符了。
- 这个特性在 <link linkend="dgui_misc_whitespace">后续内容</link> 中会解释到。</para>
- </note>
- </section>
-
- <section xml:id="dgui_template_directives">
- <title>指令</title>
-
- <indexterm>
- <primary><#...></primary>
- </indexterm>
-
- <indexterm>
- <primary>#</primary>
- </indexterm>
-
- <anchor xml:id="term.designer.directive"/>
-
- <remark>请注意,表达式章节是基于本章之上的,而插值章节是基于表达式章节的。
- 因此,指令指令章节就是基本内容后的第一部分内容。</remark>
-
- <para><indexterm>
- <primary>directive</primary>
- </indexterm>使用 FTL标签来调用 <emphasis role="term">指令</emphasis>。
- 在示例中已经调用了 <literal>list</literal> 指令。在语法上我们使用了两个标签:
- <literal><#list animals as animal></literal>
- 和 <literal></#list></literal>。</para>
-
- <para><indexterm>
- <primary>FTL tag</primary>
- </indexterm>FTL 标签分为两种:</para>
-
- <itemizedlist>
- <listitem>
- <para>开始标签:
- <literal><#<replaceable>directivename</replaceable>
- <replaceable>parameters</replaceable>></literal></para>
- </listitem>
-
- <listitem>
- <para>结束标签:
- <literal></#<replaceable>directivename</replaceable>></literal></para>
- </listitem>
- </itemizedlist>
-
- <para>除了标签以 <literal>#</literal> 开头外,其他都和HTML,XML的语法很相似。
- 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。
- 例如 <literal><#if
- <replaceable>something</replaceable>><replaceable>...</replaceable></#if></literal>,
- 而FreeMarker知道 <literal><#include <replaceable>something</replaceable>></literal> 中的
- <literal>include</literal> 指令没有可嵌套的内容。</para>
-
- <para><literal><replaceable>parameters</replaceable></literal> 的格式由
- <literal><replaceable>directivename</replaceable></literal>来决定。</para>
-
- <para>事实上,指令有两种类型: <link
- linkend="gloss.predefinedDirective">预定义指令</link>
- 和 <link linkend="gloss.userDefinedDirective">用户自定义指令</link>。
- 对于用户自定义的指令使用 <literal>@</literal> 来代替
- <literal>#</literal>,比如,<literal><@mydirective
- <replaceable>parameters</replaceable>><replaceable>...</replaceable></@mydirective></literal>。
- 更深的区别在于如果指令没有嵌套内容,那么必须这么使用 <literal><@mydirective
- <replaceable>parameters</replaceable> /></literal>,这和XML语法很相似
- (例如 <literal><img <replaceable>...</replaceable> /></literal>)。
- 但用户自定义指令是更高级的话题,将会在 <link
- linkend="dgui_misc_userdefdir">后续章节</link> 中继续讨论。</para>
-
- <para>像HTML标签一样,FTL标签也必须正确地嵌套使用。下面这段示例代码就是错的,
- 因为 <literal>if</literal> 指令在 <literal>list</literal> 指令嵌套内容的内外都有:</para>
-
- <programlisting role="template"><ul>
-<emphasis><#list animals as animal></emphasis>
- <li>${animal.name} for ${animal.price} Euros
- <emphasis><#if user == "Big Joe"></emphasis>
- (except for you)
-<emphasis></#list></emphasis> <#-- WRONG! The "if" has to be closed first. -->
-<emphasis></#if></emphasis>
-</ul></programlisting>
-
- <para>请注意,FreeMarker 仅仅关心FTL标签的嵌套而不关心HTML标签的嵌套。
- 它只会把HTML看做是文本,不会来解释HTML。</para>
-
- <para>如果你尝试使用一个不存在的指令(比如,输错了指令的名称),
- FreeMarker 就会拒绝执行模板,同时抛出错误信息。</para>
-
- <para>FreeMarker会忽略FTL标签中多余的 <link
- linkend="gloss.whiteSpace">空白标记</link>,所以也可以这么来写代码:</para>
-
- <programlisting role="template"><phrase role="markedText"><phrase
- role="markedFTLTag"><#list<phrase role="markedInvisibleText">[BR]</phrase>
- animals as<phrase role="markedInvisibleText">[BR]</phrase>
- animal<phrase role="markedInvisibleText">[BR]</phrase>
-></phrase><phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInterpolation">${animal.name}</phrase> for <phrase
- role="markedInterpolation">${animal.price}</phrase> Euros<phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"></#list ></phrase></phrase></programlisting>
-
- <para>当然,也不能在 <literal><</literal>,<literal></</literal>
- 和指令名中间入空白标记。</para>
-
- <para>所有指令的详细介绍可以在 <xref linkend="ref_directives"/>
- 中找到(但是建议您先看表达式章节)。</para>
-
- <note>
- <para>通过配置,FreeMarker 可以在FTL标签和FTL注释中,
- 使用 <literal>[</literal> 和 <literal>]</literal> 来代替
- <literal><</literal> 和 <literal>></literal>,就像
- <literal>[#if user == "Big Joe"]<replaceable>...</replaceable>[/#if]</literal>。
- 要获取更多信息,请参考:<xref linkend="dgui_misc_alternativesyntax"/>。</para>
- </note>
-
- <note>
- <para>通过配置,FreeMarker 可以不需要 <literal>#</literal>
- 来理解预定义指令(比如 <literal><if user
- == "Big Joe"><replaceable>...</replaceable></if></literal>)。
- 而我们不建议这样来使用。要获取更多信息,请参考:<xref linkend="ref_depr_oldsyntax"/>。</para>
- </note>
- </section>
-
- <section xml:id="dgui_template_exp">
- <title>表达式</title>
-
- <para><indexterm>
- <primary>expression</primary>
- </indexterm>当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。
- 例如,我们设x为8,y为5,那么 <literal>(x + y)/2</literal>
- 的值就会被处理成数字类型的值6.5。</para>
-
- <para>在我们展开细节之前,先来看一些具体的例子:</para>
-
- <itemizedlist>
- <listitem>
- <para>当给插值提供值时:插值的使用方式为
- <literal>${<replaceable>expression</replaceable>}</literal>,
- 把它放到你想输出文本的位置上,然后给值就可以打印出来了。
- 即 <literal>${(5 + 8)/2}</literal> 会打印出 ''6.5'' 来
- (如果输出的语言不是美国英语,也可能打印出''6,5''来)。</para>
- </listitem>
-
- <listitem>
- <para>当给指令参数提供值时:在入门章节我们已经看到
- <literal>if</literal> 指令的使用了。这个指令的语法是:<literal><#if
- <replaceable>expression</replaceable>><replaceable>...</replaceable></#if></literal>。
- 这里的表达式计算结果必须是布尔类型的。比如
- <literal><#if 2 < 3></literal> 中的 <literal>2
- <3</literal> (2小于3)是结果为 <literal>true</literal> 的布尔表达式。</para>
- </listitem>
- </itemizedlist>
-
- <section xml:id="exp_cheatsheet">
- <title>快速浏览(备忘单)</title>
-
- <para>这里给已经了解 FreeMarker 的人或有经验的程序员的提个醒:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="dgui_template_exp_direct">直接指定值</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_direct_string">字符串</link>:
- <literal>"Foo"</literal> 或者 <literal>'Foo'</literal> 或者
- <literal>"It's \"quoted\""</literal> 或者 <literal>'It\'s
- "quoted"'</literal> 或者
- <literal>r"C:\raw\string"</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_direct_number">数字</link>:
- <literal>123.45</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_direct_boolean">布尔值</link>:
- <literal>true</literal>, <literal>false</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_direct_seuqence">序列</link>:
- <literal>["foo", "bar", 123.45]</literal>; 值域:
- <literal>0..9</literal>, <literal>0..<10</literal> (或
- <literal>0..!10</literal>), <literal>0..</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_direct_hash">哈希表</link>:
- <literal>{"name":"green mouse",
- "price":150}</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_var">检索变量</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_var_toplevel">顶层变量</link>:
- <literal>user</literal></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_var_hash">从哈希表中检索数据</link>: <literal>user.name</literal>,
- <literal>user["name"]</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_var_sequence">从序列中检索数据</link>:
- <literal>products[5]</literal></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_var_special">特殊变量</link>: <literal>.main</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_stringop">字符串操作</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_stringop_interpolation">插值(或连接)</link>:
- <literal>"Hello ${user}!"</literal> (或 <literal>"Hello
- " + user + "!"</literal>)</para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_get_character">获取一个字符</link>:
- <literal>name[0]</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_stringop_slice">字符串切分:</link> 包含结尾:
- <literal>name[0..4]</literal>,不包含结尾:
- <literal>name[0..<5]</literal>,基于长度(宽容处理):
- <literal>name[0..*5]</literal>,去除开头:
- <literal>name[5..]</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_sequenceop">序列操作</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_sequenceop_cat">连接</link>:
- <literal>users + ["guest"]</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_seqenceop_slice">序列切分</link>:包含结尾:
- <literal>products[20..29]</literal>, 不包含结尾:
- <literal>products[20..<30]</literal>,基于长度(宽容处理):
- <literal>products[20..*10]</literal>,去除开头:
- <literal>products[20..]</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_hashop">哈希表操作</link></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_hashop_cat">连接</link>:
- <literal>passwords + { "joe": "secret42" }</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_arit">算术运算</link>:
- <literal>(x * 1.5 + 10) / 2 - y %
- 100</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_comparison">比较运算</link>:
- <literal>x == y</literal>, <literal>x != y</literal>,
- <literal>x < y</literal>, <literal>x > y</literal>,
- <literal>x >= y</literal>, <literal>x <= y</literal>,
- <literal>x lt y</literal>, <literal>x lte y</literal>,
- <literal>x gt y</literal>, <literal>x gte y</literal>,
- 等等。。。。。。</para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_logicalop">逻辑操作</link>:
- <literal>!registered && (firstVisit
- || fromEurope)</literal></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="dgui_template_exp_builtin">内建函数</link>:
- <literal>name?upper_case</literal>,
- <literal>path?ensure_starts_with('/')</literal></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_methodcall">方法调用</link>:
- <literal>repeat("What", 3)</literal></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_missing">处理不存在的值</link>:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link
- linkend="dgui_template_exp_missing_default">默认值</link>:
- <literal>name!"unknown"</literal> 或者
- <literal>(user.name)!"unknown"</literal> 或者
- <literal>name!</literal> 或者
- <literal>(user.name)!</literal></para>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_missing_test">检测不存在的值</link>: <literal>name??</literal> 或者
- <literal>(user.name)??</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><link linkend="dgui_template_exp_assignment">赋值操作</link>:
- <literal>=</literal>, <literal>+=</literal>,
- <literal>-=</literal>, <literal>*=</literal>,
- <literal>/=</literal>, <literal>%=</literal>,
- <literal>++</literal>, <literal>--</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>请参考: <link
- linkend="dgui_template_exp_precedence">运算符优先级</link></para>
- </section>
-
- <section xml:id="dgui_template_exp_direct">
- <title>直接确定值</title>
-
- <indexterm>
- <primary>literal</primary>
- </indexterm>
-
- <indexterm>
- <primary>constant</primary>
- </indexterm>
-
- <para>通常我们喜欢是使用直接确定的值而不是计算的结果。</para>
-
- <section xml:id="dgui_template_exp_direct_string">
- <title>字符串</title>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>literal</secondary>
- </indexterm>
-
- <para>在文本中确定字符串值的方法是看双引号,比如:
- <literal>"some text"</literal>,或单引号,比如:
- <literal>'some text'</literal>。这两种形式是等同的。
- 如果文本自身包含用于字符引用的引号
- ( <literal>"</literal> 或 <literal>'</literal>)或反斜杠时,
- 应该在它们的前面再加一个反斜杠;这就是转义。
- 转义允许直接在文本中输入任何字符,
- 也包括<link linkend="gloss.lineBreak">换行</link>。例如:</para>
-
- <programlisting role="template">${"It's \"quoted\" and
-this is a backslash: \\"}
-
-${'It\'s "quoted" and
-this is a backslash: \\'}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">It's "quoted" and
-this is a backslash: \
-
-It's "quoted" and
-this is a backslash: \</programlisting>
-
- <note>
- <para>这里当然可以直接在模板中输入文本而不需要
- <literal>${<replaceable>...</replaceable>}</literal>。
- 但是我们在这里用它只是为了示例来说明表达式的使用。</para>
- </note>
-
- <anchor xml:id="topic.escapeSequence"/>
-
- <indexterm>
- <primary>escape sequences</primary>
- </indexterm>
-
- <para>下面的表格是FreeMarker支持的所有转义字符。
- 在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>转义序列</th>
-
- <th>含义</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>\"</literal></td>
-
- <td>引号 (u0022)</td>
- </tr>
-
- <tr>
- <td><literal>\'</literal></td>
-
- <td>单引号(又称为撇号) (u0027)</td>
- </tr>
-
- <tr>
- <td><literal>\{</literal></td>
-
- <td>起始花括号:<literal>{</literal></td>
- </tr>
-
- <tr>
- <td><literal>\\</literal></td>
-
- <td>反斜杠 (u005C)</td>
- </tr>
-
- <tr>
- <td><literal>\n</literal></td>
-
- <td>换行符 (u000A)</td>
- </tr>
-
- <tr>
- <td><literal>\r</literal></td>
-
- <td>回车 (u000D)</td>
- </tr>
-
- <tr>
- <td><literal>\t</literal></td>
-
- <td>水平制表符(又称为tab) (u0009)</td>
- </tr>
-
- <tr>
- <td><literal>\b</literal></td>
-
- <td>退格 (u0008)</td>
- </tr>
-
- <tr>
- <td><literal>\f</literal></td>
-
- <td>换页 (u000C)</td>
- </tr>
-
- <tr>
- <td><literal>\l</literal></td>
-
- <td>小于号:<literal><</literal></td>
- </tr>
-
- <tr>
- <td><literal>\g</literal></td>
-
- <td>大于号:<literal>></literal></td>
- </tr>
-
- <tr>
- <td><literal>\a</literal></td>
-
- <td>&符:<literal>&</literal></td>
- </tr>
-
- <tr>
- <td><literal>\x<replaceable>Code</replaceable></literal></td>
-
- <td>字符的16进制 <link linkend="gloss.unicode">Unicode</link> 码 (<link
- linkend="gloss.UCS">UCS</link> 码)</td>
- </tr>
- </tbody>
- </informaltable>
-
- <para>在 <literal>\x</literal> 之后的
- <literal><replaceable>Code</replaceable></literal>
- 是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号:
- <literal>"\xA9 1999-2001"</literal>,
- <literal>"\x0A9 1999-2001"</literal>,
- <literal>"\x00A9 1999-2001"</literal>。
- 如果紧跟16进制码后一位的字符也能解释成16进制码时,
- 就必须把4位补全,否则FreeMarker就会误解你的意图。</para>
-
- <para>请注意,字符序列 <literal>${</literal> (和 <literal>#{</literal>)
- 有特殊的含义,它们被用做插入表达式的数值(典型的应用是变量的值:
- <literal>"Hello ${user}!"</literal>)。这将在 <link
- linkend="dgui_template_exp_stringop_interpolation">后续章节</link>中解释。
- 如果想要打印 <literal>${</literal> 或 <literal>#{</literal>,
- 就要使用下面所说的原生字符串,或者进行转义。就像
- <literal>"foo $\{bar}"</literal>中的 <literal>{</literal>。</para>
-
- <indexterm>
- <primary>raw string literal</primary>
- </indexterm>
-
- <para>原生字符串是一种特殊的字符串。在原生字符串中,
- 反斜杠和 <literal>${</literal> 没有特殊含义,
- 它们被视为普通的字符。为了表明字符串是原生字符串,
- 在开始的引号或单引号之前放置字母<literal>r</literal>,例如:</para>
-
- <programlisting role="template">${r"${foo}"}
-${r"C:\foo\bar"}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">${foo}
-C:\foo\bar</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_direct_number">
- <title>数字</title>
-
- <indexterm>
- <primary>number</primary>
-
- <secondary>literal</secondary>
- </indexterm>
-
- <para>输入不带引号的数字就可以直接指定一个数字,
- 必须使用点作为小数的分隔符而不能是其他的分组分隔符。
- 可以使用 <literal>-</literal> 或 <literal>+</literal>
- 来表明符号 (<literal>+</literal> 是多余的)。
- 科学记数法暂不支持使用 (<literal>1E3</literal> 就是错误的),
- 而且也不能在小数点之前不写0(<literal>.5</literal> 也是错误的)。</para>
-
- <para>下面的数字都是合法的:<literal>0.08</literal>,
- <literal>-5.013</literal>,<literal>8</literal>,
- <literal>008</literal>,<literal>11</literal>,
- <literal>+11</literal></para>
-
- <para>请注意,像 <literal>08</literal>、
- <literal>+8</literal>、 <literal>8.00</literal> 和
- <literal>8</literal> 这样的数值是完全等同的,它们都是数字8。
- 所以, <literal>${08}</literal>、<literal>${+8}</literal>、
- <literal>${8.00}</literal> 和 <literal>${8}</literal>
- 的输出都是一样的。</para>
- </section>
-
- <section xml:id="dgui_template_exp_direct_boolean">
- <title>布尔值</title>
-
- <indexterm>
- <primary>boolean</primary>
-
- <secondary>literal</secondary>
- </indexterm>
-
- <indexterm>
- <primary>literal</primary>
-
- <secondary>boolean</secondary>
- </indexterm>
-
- <para>直接写 <literal>true</literal> 或者
- <literal>false</literal> 就表示一个布尔值了,不需使用引号。</para>
- </section>
-
- <section xml:id="dgui_template_exp_direct_seuqence">
- <title>序列</title>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>literal</secondary>
- </indexterm>
-
- <indexterm>
- <primary>numerical sequence</primary>
- </indexterm>
-
- <indexterm>
- <primary>numerical range expression</primary>
- </indexterm>
-
- <indexterm>
- <primary>range expression</primary>
- </indexterm>
-
- <para>指定一个文字的序列,使用逗号来分隔其中的每个 <link
- linkend="topic.dataModel.subVar">子变量</link>,
- 然后把整个列表放到方括号中。例如:</para>
-
- <programlisting role="template"><#list <emphasis>["foo", "bar", "baz"]</emphasis> as x>
-${x}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">foo
-bar
-baz
- </programlisting>
-
- <para>列表中的项目是表达式,那么也可以这样做:
- <literal>[2 + 2, [1, 2, 3, 4], "foo"]</literal>。
- 其中第一个子变量是数字4,第二个子变量是一个序列,
- 第三个子变量是字符串"foo"。</para>
- </section>
-
- <section xml:id="dgui_template_exp_direct_ranges">
- <title>值域</title>
-
- <para>值域也是序列,但它们由指定包含的数字范围所创建,
- 而不需指定序列中每一项。比如:
- <literal>0..<m</literal>,这里假定 <literal>m</literal>
- 变量的值是5,那么这个序列就包含
- <literal>[0, 1, 2, 3, 4]</literal>。值域的主要作用有:使用
- <literal><#list<replaceable>...</replaceable>></literal>
- 来迭代一定范围内的数字,<link
- linkend="dgui_template_exp_seqenceop_slice">序列切分</link> 和
- <link linkend="dgui_template_exp_stringop_slice">字符串切分</link>。</para>
-
- <para>值域表达式的通用形式是(
- <literal><replaceable>start</replaceable></literal> 和
- <literal><replaceable>end</replaceable></literal>
- 可以是任意的结果为数字表达式):</para>
-
- <itemizedlist>
- <listitem>
- <para><literal><replaceable>start</replaceable>..<replaceable>end</replaceable></literal>:
- 包含结尾的值域。比如 <literal>1..4</literal> 就是
- <literal>[1, 2, 3, 4]</literal>, 而 <literal>4..1</literal>
- 就是 <literal>[4, 3, 2, 1]</literal>。当心一点,
- 包含结尾的值域不会是一个空序列,所以 <literal>0..length-1</literal>
- 就是 <emphasis>错误的</emphasis>,因为当长度是 <literal>0</literal> 时,
- 序列就成了 <literal>[0, -1]</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>start</replaceable>..<<replaceable>end</replaceable></literal>
- 或
- <literal><replaceable>start</replaceable>..!<replaceable>end</replaceable></literal>:
- 不包含结尾的值域。比如 <literal>1..<4</literal> 就是
- <literal>[1, 2, 3]</literal>,<literal>4..<1</literal>
- 就是 <literal>[4, 3, 2]</literal>, 而 <literal>1..<1</literal>
- 表示 <literal>[]</literal>。请注意最后一个示例;
- 结果可以是空序列,和 <literal>..<</literal>
- 和 <literal>..!</literal> 没有区别; 最后这种形式在应用程序中使用了
- <literal><</literal> 字符而引发问题(如HTML编辑器等)。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>start</replaceable>..*<replaceable>length</replaceable></literal>:
- 限定长度的值域,比如 <literal>10..*4</literal> 就是
- <literal>[10, 11, 12, 13]</literal>,<literal>10..*-4</literal>
- 就是 <literal>[10, 9, 8, 7]</literal>,而 <literal>10..*0</literal>
- 表示 <literal>[]</literal>。当这些值域被用来切分时,
- 如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考
- <link linkend="dgui_template_exp_seqenceop_slice">序列切分</link> 来获取更多信息。</para>
-
- <note>
- <para>限定长度的值域是在 FreeMarker 2.3.21版本中引入的。</para>
- </note>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>start</replaceable>..</literal>:
- 无右边界值域。这和限制长度的值域很像,只是长度是无限的。
- 比如 <literal>1..</literal> 就是
- <literal>[1, 2, 3, 4, 5, 6, ... ]</literal>,直到无穷大。
- 但是处理(比如列表显示)这种值域时要万分小心,处理所有项时,
- 会花费很长时间,直到内存溢出应用程序崩溃。
- 和限定长度的值域一样,当它们被切分时,
- 遇到切分后的序列或字符串结尾时,切分就结束了。</para>
-
- <warning>
- <para>无右边界值域在 FreeMarker 2.3.21 版本以前只能用于切分,
- 若用于其它用途,它就像空序列一样了。要使用新的特性,
- 使用 FreeMarker 2.3.21 版本是不够的,程序员要设置
- <literal>incompatible_improvements</literal> 至少到2.3.21版本。</para>
- </warning>
- </listitem>
- </itemizedlist>
-
- <para>值域的进一步注意事项:</para>
-
- <itemizedlist>
- <listitem>
- <para>值域表达式本身并没有方括号,比如这样编写代码
- <literal><#assign myRange = 0..<x></literal>,
- 而不是 <literal><#assign myRange = [0..<x]></literal>。
- 后者会创建一个包含值域的序列。方括号是切分语法的一部分,就像
- <literal><replaceable>seq</replaceable>[<replaceable>myRange</replaceable>]</literal>。</para>
- </listitem>
-
- <listitem>
- <para>可以在 <literal>..</literal> 的两侧编写算术表达式而不需要圆括号,
- 就像 <literal>n + 1 ..< m / 2 - 1</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>..</literal>,<literal>..<</literal>,
- <literal>..!</literal> 和 <literal>..*</literal> 是运算符,
- 所以它们中间不能有空格。就像 <literal>n .. <m</literal>
- 这样是错误的,但是 <literal>n ..< m</literal> 这样就可以。</para>
- </listitem>
-
- <listitem>
- <para>无右边界值域的定义大小是2147483647 (如果
- <literal>incompatible_improvements</literal> 低于2.3.21版本,那么就是0),
- 这是由于技术上的限制(32位)。但当列表显示它们的时候,实际的长度是无穷大。</para>
- </listitem>
-
- <listitem>
- <para>值域并不存储它们包含的数字,那么对于 <literal>0..1</literal> 和
- <literal>0..100000000</literal> 来说,创建速度都是一样的,
- 并且占用的内存也是一样的。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="dgui_template_exp_direct_hash">
- <title>哈希表</title>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>literal</secondary>
- </indexterm>
-
- <indexterm>
- <primary>literal</primary>
-
- <secondary>hash</secondary>
- </indexterm>
-
- <para>在模板中指定一个哈希表,就可以遍历用逗号分隔开的"键/值"对,
- 把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如:
- <literal>{ "name": "green mouse", "price": 150 }</literal>。
- 请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型,
- 而值可以是任意类型。</para>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_var">
- <title>检索变量</title>
-
- <section xml:id="dgui_template_exp_var_toplevel">
- <title>顶层变量</title>
-
- <indexterm>
- <primary>subvariable</primary>
-
- <secondary>accessing</secondary>
- </indexterm>
-
- <para>访问顶层的变量,可以简单地使用变量名。例如,
- 用表达式 <literal>user</literal> 就可以在根上获取以
- <quote>user</quote> 为名存储的变量值。然后打印出存储在里面的内容:</para>
-
- <programlisting role="template">${user}</programlisting>
-
- <para>如果没有顶层变量,那么 FreeMarker 在处理表达式时就会发生错误,
- 进而终止模板的执行(除非程序员事先配置了 FreeMarker)。</para>
-
- <para>在这种表达式中,变量名只可以包含字母(也可以是非拉丁文),
- 数字(也可以是非拉丁数字),下划线 (<literal>_</literal>),
- 美元符号 (<literal>$</literal>),at符号 (<literal>@</literal>)。
- 此外,第一个字符不可以是ASCII码数字(<literal>0</literal>-<literal>9</literal>)。
- 从 FreeMarker 2.3.22 版本开始,变量名在任何位置也可以包含负号
- (<literal>-</literal>),点(<literal>.</literal>)和冒号(<literal>:</literal>),
- 但这些必须使用前置的反斜杠(<literal>\</literal>)来转义,
- 否则它们将被解释成操作符。比如,读取名为<quote>data-id</quote>的变量,
- 表达式为 <literal>data\-id</literal>,因为 <literal>data-id</literal>
- 将被解释成 <quote>data minus id</quote>。
- (请注意,这些转义仅在标识符中起作用,而不是字符串中。)</para>
- </section>
-
- <section xml:id="dgui_template_exp_var_hash">
- <title>从哈希表中检索数据</title>
-
- <indexterm>
- <primary>subvariable</primary>
-
- <secondary>accessing</secondary>
- </indexterm>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>accessing subvariable</secondary>
- </indexterm>
-
- <para>如果有一个表达式的结果是哈希表,
- 那么我们可以使用点和子变量的名字得到它的值,
- 假设我们有如下的数据模型:</para>
-
- <programlisting role="dataModel">(root)
- |
- +- book
- | |
- | +- title = "Breeding green mouses"
- | |
- | +- author
- | |
- | +- name = "Julia Smith"
- | |
- | +- info = "Biologist, 1923-1985, Canada"
- |
- +- test = "title"</programlisting>
-
- <para>现在,就可以通过<literal>book.title</literal>
- 来读取 <literal>title</literal>,book表达式将返回一个哈希表
- (就像上一章中解释的那样)。按这种逻辑进一步来说,我们可以使用表达式
- <literal>book.author.name</literal> 来读取到auther的name。</para>
-
- <para>如果我们想指定同一个表达式的子变量,那么还有另外一种语法格式:
- <literal>book["title"]</literal>。在方括号中可以给出任意长度字符串的表达式。
- 在上面这个数据模型示例中还可以这么来获取title: <literal>book[test]</literal>。
- 下面这些示例它们含义都是相等的: <literal>book.author.name</literal>,
- <literal>book["author"].name</literal>,
- <literal>book.author.["name"]</literal>,
- <literal>book["author"]["name"]</literal>。</para>
-
- <para>当使用点式语法时,顶层变量名的命名也有相同的限制
- (命名时只能使用字母,数字,<literal>_</literal>,<literal>$</literal>,
- <literal>@</literal>,但是不能使用 <literal>0</literal>-<literal>9</literal>开头,
- 同时,从2.3.22版本开始,也可以使用 <literal>\-</literal>,<literal>\.</literal>
- 和 <literal>\:</literal>)。当使用方括号语法时,则没有这样的限制,
- 因为名称可以是任意表达式的结果。(请注意,对于FreeMarker的XML支持来说,
- 如果子变量名称是 <literal>*</literal> (星号)
- 或者 <literal>**</literal>,那么就不要使用方括号语法。)</para>
-
- <para>对于顶层变量来说,如果尝试访问一个不存在的变量也会引起错误导致解析执行模板中断
- (除非程序员事先配置过FreeMarker)。</para>
- </section>
-
- <section xml:id="dgui_template_exp_var_sequence">
- <title>从序列中检索数据</title>
-
- <indexterm>
- <primary>subvariable</primary>
-
- <secondary>accessing</secondary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>accessing subvariable</secondary>
- </indexterm>
-
- <para>这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行,
- 而且方括号内的表达式最终必须是一个数字而不是字符串。比如,要从
- <link linkend="example.stdDataModel">示例数据模型</link> 中获取第一个动物的名字
- (记住第一项数字索引是0而不是1),可以这么来写: <literal>animals[0].name</literal></para>
- </section>
-
- <section xml:id="dgui_template_exp_var_special">
- <title>特殊变量</title>
-
- <indexterm>
- <primary>special variables</primary>
- </indexterm>
-
- <para>特殊变量是由FreeMarker引擎本身定义的。
- 使用它们,可以按照如下语法形式来进行:
- <literal>.<replaceable>variable_name</replaceable></literal>。.</para>
-
- <para>通常情况下是不需使用特殊变量,而对专业用户来说可能用到。
- 所有特殊变量的说明可以参见 <link linkend="ref_specvar">参考手册</link>。</para>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_stringop">
- <title>字符串操作</title>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>operations</secondary>
- </indexterm>
-
- <section xml:id="dgui_template_exp_stringop_interpolation">
- <title>插值 (或连接)</title>
-
- <indexterm>
- <primary>interpolation</primary>
- </indexterm>
-
- <indexterm>
- <primary>concatenate strings</primary>
- </indexterm>
-
- <indexterm>
- <primary>joining strings</primary>
- </indexterm>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>concatenate</secondary>
- </indexterm>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>interpolation</secondary>
- </indexterm>
-
- <indexterm>
- <primary>adding strings</primary>
- </indexterm>
-
- <para>如果要在字符串中插入表达式的值,可以在字符串的文字中使用
- <literal>${<replaceable>...</replaceable>}</literal>
- (已经废弃的 <literal>#{<replaceable>...</replaceable>}</literal>)。
- <literal>${<replaceable>...</replaceable>}</literal> 在字符串中的作用和在
- <link linkend="dgui_template_valueinsertion"> <phrase role="markedText">文本</phrase>
- 区是相同的</link> (它遵守相同的 <emphasis>本地化敏感</emphasis> 的数字和日期/时间格式),
- 而不是 <link linkend="ref.directive.escape">自动转义</link>。</para>
-
- <para>示例 (假设user是 ''Big Joe''):</para>
-
- <programlisting role="template"><#assign s = "Hello ${user}!">
-${s} <#-- Just to see what the value of s is -->
-</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Hello Big Joe!</programlisting>
-
- <warning>
- <para>用户所犯的一个常见错误是将插值放在了不需要/不应该使用的地方。
- 插值 <emphasis>仅</emphasis> 在 <link
- linkend="dgui_template_overallstructure"><phrase
- role="markedText">文本</phrase> 区</link> 中有效。(比如,
- <literal><h1>Hello ${name}!</h1></literal>) 还有在字符串值中
- (比如, <literal><#include "/footer/${company}.html"></literal>)。
- 典型的 <emphasis>错误</emphasis> 使用是
- <literal><#if ${big}>...</#if></literal>,
- 这会导致语法错误。简单写为
- <literal><#if big>...</#if></literal>即可。
- 而且, <literal><#if "${big}">...</#if></literal>
- 也是 <emphasis>错误的</emphasis>,
- 因为它将参数值转换为字符串,但是 <literal>if</literal> 指令只接受布尔值,
- 那么这将导致运行时错误。</para>
- </warning>
-
- <para>另外,也可以使用 <literal>+</literal> 号来达到类似的效果:</para>
-
- <programlisting role="template"><#assign s = "Hello " + user + "!"></programlisting>
-
- <para>这样的效果和使用 <literal>${<replaceable>...</replaceable>}</literal> 是一样的。</para>
-
- <warning>
- <para>因为 <literal>+</literal> 和使用
- <literal>${<replaceable>...</replaceable>}</literal>
- 的规则相同,附加的字符串受到 <literal>locale</literal>,
- <literal>number_format</literal>,<literal>date_format</literal>,
- <literal>time_format</literal>,<literal>datetime_format</literal> 和
- <literal>boolean_format</literal> 等等设置的影响,
- 这是对人来说的,而不是通常机器的解析。默认情况下,这会导致数字出问题,
- 因为很多地区使用分组(千分位分隔符),那么
- <literal>"someUrl?id=" + id</literal> 就可能会是
- <literal>"someUrl?id=1 234"</literal>。
- 要预防这种事情的发生,请使用 <literal>?c</literal>
- (对计算机来说)内建函数,那么在 <literal>"someUrl?id=" + id?c</literal> 或
- <literal>"someUrl?id=${id?c}"</literal>中,
- 就会得到如 <literal>"someUrl?id=1234"</literal>
- 这样的输出, 而不管本地化和格式的设置是什么。</para>
- </warning>
- </section>
-
- <section xml:id="dgui_template_exp_get_character">
- <title>获取字符</title>
-
- <indexterm>
- <primary>charAt</primary>
- </indexterm>
-
- <indexterm>
- <primary>get character</primary>
- </indexterm>
-
- <para>在给定索引值时可以获取字符串中的一个字符,这和 <link
- linkend="dgui_template_exp_var_sequence">序列的子变量</link>是相似的,
- 比如 <literal>user[0]</literal>。这个操作执行的结果是一个长度为1的字符串,
- FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字,
- 范围是从0到字符串的长度,否则模板的执行将会发生错误并终止。</para>
-
- <para>由于序列的子变量语法和字符的getter语法冲突,
- 那么只能在变量不是序列时使用字符的getter语法(因为FTL支持多类型值,所以它是可能的),
- 这种情况下使用序列方式就比较多。(为了变通,可以使用 <link
- linkend="ref_builtin_string_for_string">
- 内建函数 <literal>string</literal> </link>,比如
- <literal>user?string[0]</literal>。不必担心你不理解这是什么意思,
- 内建函数将会在后续章节中讨论。)</para>
-
- <para>示例(假设 user 是 <quote>Big Joe</quote>):</para>
-
- <programlisting role="template">${user[0]}
-${user[4]}</programlisting>
-
- <para>将会输出(请注意第一个字符的索引是0):</para>
-
- <programlisting role="output">B
-J</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_stringop_slice">
- <title>字符串切分 (子串)</title>
-
- <indexterm>
- <primary>string slicing</primary>
- </indexterm>
-
- <indexterm>
- <primary>substring</primary>
- </indexterm>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>slice</secondary>
- </indexterm>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>substring</secondary>
- </indexterm>
-
- <para>可以按照 <link
- linkend="dgui_template_exp_seqenceop_slice">切分序列</link>
- (请参看)的相同方式来切分字符串,这就是使用字符来代替序列。不同的是:</para>
-
- <itemizedlist>
- <listitem>
- <para>降序域不允许进行字符串切分。
- (因为不像序列那样,很少情况下会想反转字符串。
- 如果真要这样做了,那就是疏忽。)</para>
- </listitem>
-
- <listitem>
- <para>如果变量的值既是字符串又是序列(多类型值),
- 那么切分将会对序列进行,而不是字符串。当处理XML时,
- 这样的值就是普通的了。此时,可以使用
- <literal><replaceable>someXMLnode</replaceable>?string[<replaceable>range</replaceable>]</literal>。</para>
- </listitem>
-
- <listitem>
- <para>一个遗留的bug:值域 <emphasis>包含</emphasis> 结尾时,
- 结尾小于开始索引并且是是非负的(就像在 <literal>"abc"[1..0]</literal> 中),
- 会返回空字符串而不是错误。(在降序域中这应该是个错误。)
- 现在这个bug已经向后兼容,但是不应该使用它,否在就会埋下一个错误。</para>
- </listitem>
- </itemizedlist>
-
- <para>示例:</para>
-
- <programlisting role="template"><#assign s = "ABCDEF">
-${s[2..3]}
-${s[2..<4]}
-${s[2..*3]}
-${s[2..*100]}
-${s[2..]}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">CD
-CD
-CDE
-CDEF
-CDEF</programlisting>
-
- <note>
- <para>下面包括了一些使用内建函数来方便字符串切分的典型用例: <link
- linkend="ref_builtin_remove_beginning"><literal>remove_beginning</literal></link>,
- <link
- linkend="ref_builtin_remove_ending"><literal>remove_ending</literal></link>,
- <link
- linkend="ref_builtin_keep_before"><literal>keep_before</literal></link>,
- <link
- linkend="ref_builtin_keep_after"><literal>keep_after</literal></link>,
- <link
- linkend="ref_builtin_keep_before_last"><literal>keep_before_last</literal></link>,
- <link
- linkend="ref_builtin_keep_after_last"><literal>keep_after_last</literal></link></para>
- </note>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_sequenceop">
- <title>序列操作</title>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>operations</secondary>
- </indexterm>
-
- <section xml:id="dgui_template_exp_sequenceop_cat">
- <title>连接</title>
-
- <indexterm>
- <primary>concatenate sequences</primary>
- </indexterm>
-
- <indexterm>
- <primary>joining sequences</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>concatenate</secondary>
- </indexterm>
-
- <indexterm>
- <primary>adding sequences</primary>
- </indexterm>
-
- <para>序列的连接可以按照字符串那样使用 <literal>+</literal>
- 号来进行,例如:</para>
-
- <programlisting role="template"><#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
-- ${user}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- Joe
-- Fred
-- Julia
-- Kate
- </programlisting>
-
- <para>请注意,不要在很多重复连接时使用序列连接操作,
- 比如在循环中往序列上追加项目,而这样的使用是可以的:
- <literal><#list users + admins as person></literal>。
- 尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的,
- 但是最终的结果序列的读取却比原先的两个序列慢那么一点。
- 通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。</para>
- </section>
-
- <section xml:id="dgui_template_exp_seqenceop_slice">
- <title>序列切分</title>
-
- <indexterm>
- <primary>sequence slice</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>slice</secondary>
- </indexterm>
-
- <indexterm>
- <primary>subsequence</primary>
- </indexterm>
-
- <para>使用
- <literal><replaceable>seq</replaceable>[<replaceable>range</replaceable>]</literal>,
- 这里 <literal><replaceable>range</replaceable></literal> 是一个值域
- <link linkend="dgui_template_exp_direct_ranges">此处有说明</link>,
- 就可以得到序列的一个切分。结果序列会包含原序列
- (<literal><replaceable>seq</replaceable></literal>)中的项,
- 而且索引在值域中。例如:</para>
-
- <programlisting role="template"><#assert seq = ["A", "B", "C", "D", "E"]>
-<#list seq[1..3] as i>${i}</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">BCD </programlisting>
-
- <para>此外,切分后序列中的项会和值域的顺序相同。
- 那么上面的示例中,如果值域是 <literal>3..1</literal> 将会输出
- <literal>DCB</literal>。</para>
-
- <para>值域中的数字必须是序列可使用的合法索引,
- 否则模板的处理将会终止并报错。像上面的示例那样,
- <literal>seq[-1..0]</literal> 就会出错,
- 而 <literal>seq[-1]</literal> 就是合法的。
- <literal>seq[1..5]</literal> 也不对,
- 因为 <literal>seq[5]</literal> 是非法的。
- (请注意,尽管100已经越界,但是
- <literal>seq[100..<100]</literal> 或
- <literal>seq[100..*0]</literal> 是合法的,因为那些值域都是空。)</para>
-
- <para>限制长度的值域
- (<literal><replaceable>start</replaceable>..*<replaceable>length</replaceable></literal>)
- 和无右边界值域 (<literal><replaceable>start</replaceable>..</literal>)
- 适用于切分后序列的长度。它们会切分可用项中尽可能多的部分:</para>
-
- <programlisting role="template"><#assign seq = ["A", "B", "C"]>
-
-Slicing with length limited ranges:
-- <#list seq[0..*2] as i>${i}</#list>
-- <#list seq[1..*2] as i>${i}</#list>
-- <#list seq[2..*2] as i>${i}</#list> <#-- Not an error -->
-- <#list seq[3..*2] as i>${i}</#list> <#-- Not an error -->
-
-Slicing with right-unlimited ranges:
-- <#list seq[0..] as i>${i}</#list>
-- <#list seq[1..] as i>${i}</#list>
-- <#list seq[2..] as i>${i}</#list>
-- <#list seq[3..] as i>${i}</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Slicing with length limited ranges:
-- AB
-- BC
-- C
--
-
-Slicing with right-unlimited ranges:
-- ABC
-- BC
-- C
--</programlisting>
-
- <para>请注意,上面的有限长度切分和无右边界切分都允许开始索引超过最后项
- <emphasis>一个</emphasis> (但不能再多了)。</para>
-
- <note>
- <para>要对序列进行给定大小的切分,就应该使用内建函数 <link
- linkend="ref_builtin_chunk"><literal>chunk</literal></link>。</para>
- </note>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_hashop">
- <title>哈希表操作</title>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>operations</secondary>
- </indexterm>
-
- <section xml:id="dgui_template_exp_hashop_cat">
- <title>连接</title>
-
- <indexterm>
- <primary>concatenate hashes</primary>
- </indexterm>
-
- <indexterm>
- <primary>joining hashes</primary>
- </indexterm>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>concatenate</secondary>
- </indexterm>
-
- <indexterm>
- <primary>adding hashes</primary>
- </indexterm>
-
- <para>像连接字符串那样,也可以使用 <literal>+</literal>
- 号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在
- <literal>+</literal> 号右侧的哈希表中的项优先。例如:</para>
-
- <programlisting role="template"><#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
-- Joe is ${ages.Joe}
-- Fred is ${ages.Fred}
-- Julia is ${ages.Julia}
-</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- Joe is 30
-- Fred is 25
-- Julia is 18</programlisting>
-
- <para>请注意,很多项连接时不要使用哈希表连接,
- 比如在循环时往哈希表中添加新项。这和<link
- linkend="dgui_template_exp_sequenceop_cat">序列连接</link>
- 的情况是一致的。</para>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_arit">
- <title>算数运算</title>
-
- <indexterm>
- <primary>arithmetic</primary>
- </indexterm>
-
- <indexterm>
- <primary>math</primary>
- </indexterm>
-
- <indexterm>
- <primary>addition</primary>
- </indexterm>
-
- <indexterm>
- <primary>subtraction</primary>
- </indexterm>
-
- <indexterm>
- <primary>division</primary>
- </indexterm>
-
- <indexterm>
- <primary>modulus</primary>
- </indexterm>
-
- <para>算数运算包含基本的四则运算和求模运算,运算符有:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>加法: <literal>+</literal></para>
- </listitem>
-
- <listitem>
- <para>减法: <literal>-</literal></para>
- </listitem>
-
- <listitem>
- <para>乘法: <literal>*</literal></para>
- </listitem>
-
- <listitem>
- <para>除法: <literal>/</literal></para>
- </listitem>
-
- <listitem>
- <para>求模 (求余): <literal>%</literal></para>
- </listitem>
- </itemizedlist>
-
- <remark>运算精度的问题?</remark>
-
- <para>示例:</para>
-
- <programlisting role="template">${100 - x * x}
-${x / 2}
-${12 % 10}</programlisting>
-
- <para>假设 <literal>x</literal> 是 5,将会输出:</para>
-
- <programlisting role="output">75
-2.5
-2</programlisting>
-
- <para>要保证两个操作数都是结果为数字的表达式。
- 下面的这个例子在运行时,FreeMarker就会发生错误,
- 因为是字符串 <literal>"5"</literal> 而不是数字5:</para>
-
- <programlisting role="template">${3 * "5"} <#-- WRONG! --></programlisting>
-
- <para>但这种情况也有一个例外,就是 <literal>+</literal> 号,它是用来 <link
- linkend="dgui_template_exp_stringop_interpolation">连接字符串</link>的。
- 如果 <literal>+</literal> 号的一端是字符串,<literal>+</literal>
- 号的另外一端是数字,那么数字就会自动转换为字符串类型(使用当前页面语言的适当格式),
- 之后使用 <literal>+</literal> 号作为字符串连接操作符。示例如下:</para>
-
- <programlisting role="template">${3 + "5"}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">35</programlisting>
-
- <para>通常来说,FreeMarker不会自动将字符串转换为数字,反之会自动进行。</para>
-
- <para><indexterm>
- <primary>integer division</primary>
- </indexterm><indexterm>
- <primary>integer part</primary>
- </indexterm>有时我们只想获取除法计算(或其它运算)的整数部分,
- 这可以使用内建函数 <literal>int</literal> 来解决。(关于内建函数
- <link linkend="dgui_template_exp_builtin">后续章节</link>会来解释):</para>
-
- <programlisting role="template">${(x/2)?int}
-${1.1?int}
-${1.999?int}
-${-1.1?int}
-${-1.999?int}</programlisting>
-
- <para>假设 <literal>x</literal> 是 5,将会输出:</para>
-
- <programlisting role="output">2
-1
-1
--1
--1</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_comparison">
- <title>比较运算</title>
-
- <indexterm>
- <primary>comparison operators</primary>
- </indexterm>
-
- <para>有时我们需要知道两个值是否相等,或者哪个值更大一点。</para>
-
- <para>为了演示具体的例子,我们在这里使用 <literal>if</literal> 指令。
- <literal>if</literal> 指令的用法是: <literal><#if
- <replaceable>expression</replaceable>>...</#if></literal>,
- 其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。
- 如果表达式的结果是 <literal>true</literal> ,
- 那么在开始和结束标记内的内容将会被执行,否则就会被跳过。</para>
-
- <para>测试两个值相等使用 <literal>=</literal>
- (或者采用Java和C语言中的 <literal>==</literal> ;二者是完全等同的。)
- 测试两个值不等使用 <literal>!=</literal>。比如,
- 假设 <literal>user</literal> 是 ''Big Joe'':</para>
-
- <programlisting role="template"><#if <emphasis>user == "Big Joe"</emphasis>>
- It is Big Joe
-</#if>
-<#if <emphasis>user != "Big Joe"</emphasis>>
- It is not Big Joe
-</#if></programlisting>
-
- <para> <literal><#if ...></literal> 中的表达式
- <literal>user = "Big Joe"</literal> 就是布尔值
- <literal>true</literal>,面的代码将会输出 ''It is Big Joe''。</para>
-
- <para> <literal>=</literal> 或 <literal>!=</literal>
- 两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型
- (也就是说字符串只能和字符串来比较,数字只能和数字来比较等)否则将会出错,
- 模板执行中断。例如 <literal><#if 1 = "1"></literal> 就会导致错误。
- 请注意FreeMarker进行的是精确的比较,所以字符串在比较时要注意大小写和空格:
- <literal>"x"</literal> 和 <literal>"x "</literal>
- 和 <literal>"X"</literal> 是不同的值。</para>
-
- <para>对数字和日期类型的比较,也可以使用 <literal><</literal>,
- <literal><=</literal>,<literal>>=</literal> 和
- <literal>></literal>。不能把它们当作字符串来比较。比如:</para>
-
- <programlisting role="template"><#if x <emphasis><=</emphasis> 12>
- x is less or equivalent with 12
-</#if></programlisting>
-
- <para>使用 <literal>>=</literal> 和 <literal>></literal>
- 的时候有一点小问题。FreeMarker解释 <literal>></literal>
- 的时候可以把它当作FTL标签的结束符。为了避免这种问题,可以使用
- <literal>lt</literal> 代替 <literal><</literal>,
- <literal>lte</literal> 代替 <literal><=</literal>,
- <literal>gt</literal> 代替 <literal>></literal> 还有
- <literal>gte</literal> 代替 <literal>>=</literal>,
- 例如 <literal><#if x gt y></literal>。另外一个技巧是将表达式放到 <link
- linkend="dgui_template_exp_parentheses">圆括号</link> 中,
- 尽管这么写并不优雅,例如 <literal><#if (x > y)></literal>。</para>
-
- <note>
- <para>FreeMarker 也支持一些其它的选择,但是这些已经废弃了:</para>
-
- <itemizedlist>
- <listitem>
- <para>在可能出问题的关系标记处使用 <literal>&gt;</literal> 和
- <literal>&lt;</literal> ,就像: <literal><#if x &gt;
- y></literal> 或 <literal><#if x &gt;=
- y></literal>。 请注意通常FTL不支持标签中的实体引用(如
- <literal>&<replaceable>...</replaceable>;</literal> 这些东西);
- 做算术比较时就会有异常。</para>
- </listitem>
-
- <listitem>
- <para><literal>\lt</literal>, <literal>\lte</literal>,
- <literal>\gt</literal> 和 <literal>\gte</literal> 使用他们时,
- 不带反斜杠的效果一样。</para>
- </listitem>
- </itemizedlist>
- </note>
- </section>
-
- <section xml:id="dgui_template_exp_logicalop">
- <title>逻辑操作</title>
-
- <indexterm>
- <primary>boolean</primary>
-
- <secondary>operations</secondary>
- </indexterm>
-
- <indexterm>
- <primary>logical operations</primary>
- </indexterm>
-
- <indexterm>
- <primary>or</primary>
- </indexterm>
-
- <indexterm>
- <primary>and</primary>
- </indexterm>
-
- <indexterm>
- <primary>not</primary>
- </indexterm>
-
- <para>常用的逻辑操作符:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>逻辑 或: <literal>||</literal></para>
- </listitem>
-
- <listitem>
- <para>逻辑 与: <literal>&&</literal></para>
- </listitem>
-
- <listitem>
- <para>逻辑 非: <literal>!</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><#if x < 12 <emphasis>&&</emphasis> color == "green">
- We have less than 12 things, and they are green.
-</#if>
-<#if <emphasis>!</emphasis>hot> <#-- here hot must be a boolean -->
- It's not hot.
-</#if></programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_builtin">
- <title>内建函数</title>
-
- <indexterm>
- <primary>built-in</primary>
- </indexterm>
-
- <para>内建函数就像FreeMarker在对象中添加的方法一样。
- 要防止和实际方法和其它子变量的命名冲突,则不能使用点
- (<literal>.</literal>),这里使用问号
- (<literal>?</literal>)来和父对象分隔开。
- 比如,想要保证 <literal>path</literal> 有起始的
- <literal>/</literal> ,那么可以这么来写:
- <literal>path?ensure_starts_with('/')</literal>。
- <literal>path</literal> 后的Java对象(通常就是 <literal>String</literal>)
- 并没有这样的方法,这是FreeMarker添加的。为了简洁,如果方法没有参数,
- 那么就可以忽略 <literal>()</literal>,比如想要获取
- <literal>path</literal> 的长度,就可以写作:<literal>path?length</literal>,
- <emphasis>而不是</emphasis> <literal>path?length()</literal>。</para>
-
- <para>内建函数关键性的另外一个原因是常见的(尽管它依赖于配置的设置),
- FreeMarker不会暴露对象的Java API。那么尽管Java的 <literal>String</literal>
- 类有 <literal>length()</literal> 方法,但在模板中却是不可见的,
- 就 <emphasis>不得不</emphasis> 使用 <literal>path?length</literal> 来代替。
- 这里的优点是模板不依赖下层Java对象的精确类型。(比如某些场景中,
- <literal>path</literal> 也可能是 <literal>java.nio.Path</literal> 类型,
- 如果程序员配置了FreeMarker去暴露 <literal>Path</literal>
- 对象作为FTL字符串类型,那么模板就不会在意了,使用
- <literal>?length</literal> 也是可以的,
- 即便 <literal>java.nio.Path</literal> 没有类似的方法。)</para>
-
- <para>可以找到一些 <link
- linkend="topic.commonlyUsedBuiltIns">此处提及的常用内建函数</link>,还有
- <link linkend="ref_builtins">完整的内建函数参考</link>。
- 现在,我们只需了解一些重要的内建函数就行了:</para>
-
- <para>比如:</para>
-
- <programlisting role="template">${testString?upper_case}
-${testString?html}
-${testString?upper_case?html}
-
-${testSequence?size}
-${testSequence?join(", ")}</programlisting>
-
- <para>假设 <literal>testString</literal> 中存储了字符串
- ''Tom & Jerry'', 而testSequnce中存储了字符串
- <quote>foo</quote>, <quote>bar</quote> 和 <quote>baz</quote>,
- 将会输出:</para>
-
- <programlisting role="output">TOM & JERRY
-Tom &amp; Jerry
-TOM &amp; JERRY
-
-3
-foo, bar, baz</programlisting>
-
- <para>请注意:上面的 <literal>testString?upper_case?html</literal>。因为
- <literal>test?upper_case</literal> 的结果是字符串,那么就可以在它的上面
- 使用内建函数 <literal>html</literal>。</para>
-
- <para>很自然可以看到,内建函数的左侧可以是任意的表达式,而不仅仅是变量名:</para>
-
- <programlisting role="template">${testSeqence[1]?cap_first}
-${"horse"?cap_first}
-${(testString + " & Duck")?html}</programlisting>
-
- <programlisting role="output">Bar
-Horse
-Tom &amp; Jerry &amp; Duck</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_methodcall">
- <title>方法调用</title>
-
- <indexterm>
- <primary>call a method</primary>
- </indexterm>
-
- <indexterm>
- <primary>method</primary>
-
- <secondary>call</secondary>
- </indexterm>
-
- <para>如果有一个方法,那么可以使用方法调用操作。
- 方法调用操作是使用逗号来分割在括号内的表达式而形成参数列表,这些值就是参数。
- 方法调用操作将这些值传递给方法,然后返回一个结果。
- 这个结果就是整个方法调用表达式的值。</para>
-
- <para>假设程序员定义了一个可供调用的方法 <literal>repeat</literal>。
- 第一个参数是字符串类型,第二个参数是数字类型。方法的返回值是字符串类型,
- 而方法要完成的操作是将第一个参数重复显示,显示的次数是第二个参数设定的值。</para>
-
- <programlisting role="template">${repeat("Foo", 3)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">FooFooFoo</programlisting>
-
- <para>这里的 <literal>repeat</literal> 就是方法变量(根据如何 <link
- linkend="dgui_template_exp_var_toplevel">访问顶层变量</link>),
- <literal>("What", 3)</literal> 就调用了该方法。</para>
-
- <para>这里需要强调方法调用也是普通表达式,和其它都是一样的,所以:</para>
-
- <programlisting role="template">${repeat(repeat("x", 2), 3) + repeat("Foo", 4)?upper_case}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">xxxxxxFOOFOOFOOFOO</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_missing">
- <title>处理不存在的值</title>
-
- <note>
- <para>这些操作符是从 FreeMarker 2.3.7 版本开始引入的(用来代替内建函数
- <literal>default</literal>, <literal>exists</literal> 和
- <literal>if_exists</literal> )。</para>
- </note>
-
- <indexterm>
- <primary>undefined variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>missing variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>null</primary>
- </indexterm>
-
- <indexterm>
- <primary>handling null-s</primary>
- </indexterm>
-
- <indexterm>
- <primary>error handling</primary>
- </indexterm>
-
- <para>正如我们前面解释的那样,当试图访问一个不存在的变量时,
- FreeMarker 将会报错而导致模板执行中断。
- 通常我们可以使用两个特殊操作符来压制这个错误,控制这种错误情况。
- 被控制的变量可以是顶层变量,哈希表或序列的子变量。
- 此外这些操作符还能处理方法调用的返回值不存在的情况
- <phrase role="forProgrammers">(这点对Java程序员来说:
- 返回值是 <literal>null</literal> 或者返回值为 <literal>void</literal>
- 类型)</phrase>,通常来说,我们应该使用这些操作符来控制可能不存在的值,
- 而不仅仅是不存在的变量。</para>
-
- <para><phrase role="forProgrammers">对于知道Java中
- <literal>null</literal> 的人来说,FreeMarker 2.3.<replaceable>x</replaceable>
- 版本把它们视为不存在的变量。单地说,模板语言中没有 <literal>null</literal>
- 这个概念。比如有一个bean,bean中有一个 <literal>maidenName</literal> 属性,
- 对于模板而言(假设没有配置FreeMarker来使用一些极端的对象包装),
- 该属性的值是 <literal>null</literal>,和不存在这个属性的情况是一致的。
- 调用方法的返回值如果是 <literal>null</literal>
- 的话 FreeMarker 也会把它当作不存在的变量来处理
- (假定只使用了普通的对象包装)。可以在 <link linkend="faq_null">FAQ</link>
- 中了解更多内容。</phrase></para>
-
- <note>
- <para>如果想知道为什么 FreeMarker 对不存在的变量如此挑剔,
- 请阅读 <link linkend="faq_picky_about_missing_vars">FAQ</link> 部分。</para>
- </note>
-
- <section xml:id="dgui_template_exp_missing_default">
- <title>默认值操作符</title>
-
- <indexterm>
- <primary>default value operator</primary>
- </indexterm>
-
- <para>使用形式:
- <literal><replaceable>unsafe_expr</replaceable>!<replaceable>default_expr</replaceable></literal>
- 或 <literal><replaceable>unsafe_expr</replaceable>!</literal> or
- <literal>(<replaceable>unsafe_expr</replaceable>)!<replaceable>default_expr</replaceable></literal>
- 或
- <literal>(<replaceable>unsafe_expr</replaceable>)!</literal></para>
-
- <para>这个操作符允许你为可能不存在的变量指定一个默认值。</para>
-
- <para>例如,假设下面展示的代码中没有名为 <literal>mouse</literal> 的变量:</para>
-
- <programlisting role="template">${mouse!"No mouse."}
-<#assign mouse="Jerry">
-${mouse!"No mouse."}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">No mouse.
-Jerry</programlisting>
-
- <para>默认值可以是任何类型的表达式,也可以不必是字符串。
- 也可以这么写:<literal>hits!0</literal> 或
- <literal>colors!["red", "green", "blue"]</literal>。
- 默认值表达式的复杂程度没有严格限制,还可以这么来写:
- <literal>cargo.weight!(item.weight * itemCount + 10)</literal>。</para>
-
- <warning>
- <para>如果在 <literal>!</literal>后面有复合表达式,
- 如 <literal>1 + x</literal>,<emphasis>通常</emphasis>
- 使用括号,如 <literal>${x!(1 + y)}</literal> 或
- <literal>${(x!1) + y)}</literal>,这样就根据你的意图来确定优先级。
- 由于FreeMarker 2.3.x 版本的源码中的小失误所以必须这么来做。
- <literal>!</literal> (作为默认值操作) 右侧的优先级非常低。
- 这就意味着 <literal>${x!1 + y}</literal> 会被 FreeMarker
- 误解为 <literal>${x!(1 + y)}</literal>,而真实的意义是
- <literal>${(x!1) + y}</literal>。 这个源码错误在FreeMarker 2.4中会得到修正。
- 在编程中注意这个错误,要么就使用FreeMarker 2.4!</para>
- </warning>
-
- <para>如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。
- (这是 FreeMarker 允许多类型值的体现)请注意,如果想让默认值为
- <literal>0</literal> 或 <literal>false</literal>,则不能省略它。例如:</para>
-
- <programlisting role="template">(${mouse!})
-<#assign mouse = "Jerry">
-(${mouse!})</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">()
-(Jerry)</programlisting>
-
- <warning>
- <para>因为语法的含糊 <literal><@something a=x! b=y /></literal>
- 将会解释为 <literal><@something a=x!(b=y) /></literal>,那就是说
- <literal>b=y</literal> 将会被视为是比较运算,然后结果作为
- <literal>x</literal>的默认值,而不是想要的参数 <literal>b</literal>。
- 为了避免这种情况,如下编写代码即可: <literal><@something a=(x!) b=y
- /></literal></para>
- </warning>
-
- <para>用于非顶层变量时,默认值操作符可以有两种使用方式:</para>
-
- <programlisting role="template">product.color!"red"</programlisting>
-
- <para>如果是这样的写法,那么在 <literal>product</literal> 中,
- 当 <literal>color</literal> 不存在时(返回 <literal>"red"</literal> ),
- 将会被处理,但是如果连 <literal>product</literal> 都不存在时将不会处理。
- 也就是说这样写时变量 <literal>product</literal> 必须存在,否则模板就会报错。</para>
-
- <programlisting role="template">(product.color)!"red"</programlisting>
-
- <para>这时,如果当 <literal>product.color</literal> 不存在时也会被处理,
- 那就是说,如果 <literal>product</literal> 不存在或者
- <literal>product</literal> 存在而 <literal>color</literal>
- 不存在,都能显示默认值 <literal>"red"</literal> 而不会报错。
- 本例和上例写法的重要区别在于用括号时,
- 就允许其中表达式的任意部分可以未定义。而没有括号时,
- 仅允许表达式的最后部分可以不被定义。</para>
-
- <para>当然,默认值操作也可以作用于序列子变量,比如:</para>
-
- <programlisting role="template"><#assign seq = ['a', 'b']>
-${seq[0]!'-'}
-${seq[1]!'-'}
-${seq[2]!'-'}
-${seq[3]!'-'}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">a
-b
--
--</programlisting>
-
- <para>如果序列索引是负数(比如 <literal>seq[-1]!'-'</literal>)
- 也会发生错误,不能使用该运算符或者其它运算符去压制它。</para>
- </section>
-
- <section xml:id="dgui_template_exp_missing_test">
- <title>不存在值检测操作符</title>
-
- <indexterm>
- <primary>existence test operator</primary>
- </indexterm>
-
- <indexterm>
- <primary>missing value test operator</primary>
- </indexterm>
-
- <indexterm>
- <primary>testing for null</primary>
- </indexterm>
-
- <indexterm>
- <primary>testing for missing</primary>
- </indexterm>
-
- <indexterm>
- <primary>testing for undefined</primary>
- </indexterm>
-
- <indexterm>
- <primary>is null</primary>
- </indexterm>
-
- <para>使用形式:
- <literal><replaceable>unsafe_expr</replaceable>??</literal> 或
- <literal>(<replaceable>unsafe_expr</replaceable>)??</literal></para>
-
- <para>这个操作符告诉我们一个值是否存在。基于这种情况,
- 结果是 <literal>true</literal> 或 <literal>false</literal>。</para>
-
- <para>示例如下,假设并没有名为 <literal>mouse</literal> 的变量:</para>
-
- <programlisting role="template"><#if mouse??>
- Mouse found
-<#else>
- No mouse found
-</#if>
-Creating mouse...
-<#assign mouse = "Jerry">
-<#if mouse??>
- Mouse found
-<#else>
- No mouse found
-</#if></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> No mouse found
-Creating mouse...
- Mouse found</programlisting>
-
- <para>访问非顶层变量的使用规则和默认值操作符也是一样的,
- 也就是说,可以写 <literal>product.color??</literal>
- 和 <literal>(product.color)??</literal>。</para>
- </section>
- </section>
-
- <section xml:id="dgui_template_exp_assignment">
- <title>赋值操作符</title>
-
- <para>这些并不是表达式,只是复制指令语法的一部分,比如 <link
- linkend="ref_directive_assign"><literal>assign</literal></link>,
- <link linkend="ref_directive_local"><literal>local</literal></link>
- 和 <link
- linkend="ref_directive_global"><literal>global</literal></link>。
- 照这样,它们不能任意被使用。</para>
-
- <para><literal><#assign x += y></literal> 是
- <literal><#assign x = x + y></literal> 的简写,<literal><#assign x
- *= y></literal> 是 <literal><#assign x = x *
- y></literal>的简写等等。。。</para>
-
- <para><literal><#assign x++></literal> 和
- <literal><#assign x += 1></literal> (或 <literal><#assign x
- = x + 1></literal>)不同,它只做算术加法运算
- (如果变量不是数字的话就会失败),而其它的是进行字符串,序列连接和哈希表连接的重载。
- <literal><#assign x--></literal> 是
- <literal><#assign x -= 1></literal> 的简写。</para>
- </section>
-
- <section xml:id="dgui_template_exp_parentheses">
- <title>括号</title>
-
- <indexterm>
- <primary>parentheses</primary>
- </indexterm>
-
- <para>括号可以用来给任意表达式分组。示例如下:</para>
-
- <programlisting role="template"> <#-- Output will be: -->
-${3 * 2 + 2} <#-- 8 -->
-${3 * (2 + 2)} <#-- 12 -->
-${3 * ((2 + 2) * (1 / 2))} <#-- 6 -->
-${"green " + "mouse"?upper_case} <#-- green MOUSE -->
-${("green " + "mouse")?upper_case} <#-- GREEN MOUSE -->
-<#if !(color == "red" || color == "green")>
- The color is nor red nor green
-</#if></programlisting>
-
- <para>请注意,<link linkend="dgui_template_exp_methodcall">方法调用表达式</link>
- 使用的括号和给表达式分组的括号含义是完全不同的。</para>
- </section>
-
- <section xml:id="dgui_template_exp_whitespace">
- <title>表达式中的空格</title>
-
- <para>FTL 忽略表达式中的多余的 <link
- linkend="gloss.whiteSpace">空格</link>。下面的表示是相同的:</para>
-
- <programlisting role="template">${x + ":" + book.title?upper_case}</programlisting>
-
- <para>和</para>
-
- <programlisting role="template">${x+":"+book.title?upper_case}</programlisting>
-
- <para>和</para>
-
- <programlisting role="template">${
- x
- + ":" + book . title
- ? upper_case
- }</programlisting>
- </section>
-
- <section xml:id="dgui_template_exp_precedence">
- <title>操作符的优先级</title>
-
- <indexterm>
- <primary>precedence</primary>
- </indexterm>
-
- <indexterm>
- <primary>operator precedence</primary>
- </indexterm>
-
- <para>下面的表格显示了已定义操作符的优先级。
- 表格中的运算符按照优先程度降序排列:上面的操作符优先级高于它下面的。
- 高优先级的运算符执行要先于优先级比它低的。表格同一行上的两个操作符优先级相同。
- 当有相同优先级的二元运算符(运算符有两个''参数'',比如
- <literal>+</literal>和<literal>-</literal>)挨着出现时,它们按照从左到右的原则运算。</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>运算符组</th>
-
- <th>运算符</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td>最高优先级运算符</td>
-
- <td><literal>[<replaceable>subvarName</replaceable>]
- [<replaceable>subStringRange</replaceable>] . ?
- (<replaceable>methodParams</replaceable>)
- <replaceable>expr</replaceable>!
- <replaceable>expr</replaceable>??</literal></td>
- </tr>
-
- <tr>
- <td>一元前缀运算符</td>
-
- <td><literal>+<replaceable>expr</replaceable>
- -<replaceable>expr</replaceable> !expr</literal></td>
- </tr>
-
- <tr>
- <td>乘除法,求模运算符</td>
-
- <td><literal>* / %</literal></td>
- </tr>
-
- <tr>
- <td>加减法运算符</td>
-
- <td><literal>+ -</literal></td>
- </tr>
-
- <tr>
- <td>数字值域</td>
-
- <td><literal>..</literal> <literal>..<</literal>
- <literal>..!</literal> <literal>..*</literal></td>
- </tr>
-
- <tr>
- <td>关系运算符</td>
-
- <td><literal>< > <= >=</literal> (and equivalents:
- <literal>gt</literal>, <literal>lt</literal>, etc.)</td>
- </tr>
-
- <tr>
- <td>相等,不等运算符</td>
-
- <td><literal>== !=</literal> (and equivalents:
- <literal>=</literal>)</td>
- </tr>
-
- <tr>
- <td>逻辑 <quote>与</quote> 运算符</td>
-
- <td><literal>&&</literal></td>
- </tr>
-
- <tr>
- <td>逻辑 <quote>或</quote> 运算符</td>
-
- <td><literal>||</literal></td>
- </tr>
- </tbody>
- </informaltable>
-
- <para>如果你熟悉C语言,Java语言或JavaScript语言,
- 请注意 FreeMarker 中的优先级规则和它们是相同的,
- 除了那些只有FTL本身含有的操作符。</para>
-
- <para>因为编程的失误,默认值操作符
- (<literal><replaceable>exp</replaceable>!<replaceable>exp</replaceable></literal>)
- 不在上面的表格中,按照向后兼容的原则,在 FreeMarker 2.4 版本中将会修正它。
- 而且它将是最高优先级的运算符,但是在 FreeMarker 2.3.x
- 版本中它右边的优先级由于失误就非常低。
- 所以在默认值操作符的右边中使用复杂表达式时可以使用括号,
- 可以是 <literal>x!(y + 1)</literal> 或者是 <literal>(x!y) + 1</literal>。
- 而不能是 <literal>x!y + 1</literal>。</para>
- </section>
- </section>
-
- <section xml:id="dgui_template_valueinsertion">
- <title>插值</title>
-
- <indexterm>
- <primary>interpolation</primary>
- </indexterm>
-
- <indexterm>
- <primary>${...}</primary>
- </indexterm>
-
- <section>
- <title>概览</title>
-
- <para>插值的使用格式是:
- <literal>${<replaceable>expression</replaceable>}</literal>,这里的
- <literal><replaceable>expression</replaceable></literal>
- 可以是所有种类的表达式(比如 <literal>${100 + x}</literal>)。</para>
-
- <para>插值是用来给 <literal><replaceable>表达式</replaceable></literal>
- 插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:在 <link
- linkend="dgui_template_overallstructure"><phrase
- role="markedText">文本</phrase> 区</link> (比如
- <literal><h1>Hello ${name}!</h1></literal>) 和 <link
- linkend="dgui_template_exp_stringop_interpolation">字符串表达式</link>
- (比如 <literal><#include "/footer/${company}.html"></literal>)中。</para>
-
- <para>表达式的结果必须是字符串,数字或者日期/时间/日期-时间值,
- 因为(默认是这样)仅仅这些值可以被插值自动转换为字符串。其它类型的值
- (比如布尔值,序列)必须 <quote>手动地</quote> 转换成字符串(后续会有一些建议),
- 否则就会发生错误,中止模板执行。</para>
-
- <warning>
- <para>一个常犯的错误是在不能使用插值的地方使用了它。
- 插值 <emphasis>仅仅</emphasis> 在 <link
- linkend="dgui_template_overallstructure"><phrase
- role="markedText">文本</phrase> 区</link> (比如
- <literal><h1>Hello ${name}!</h1></literal>) 和 <link
- linkend="dgui_template_exp_direct_string">字符串</link>
- (比如 <literal><#include "/footer/${company}.html"></literal>)中起作用。
- 典型的 <emphasis>错误</emphasis> 使用是 <literal><#if
- ${big}>...</#if></literal>,这是语法上的错误。
- 只要简单写为 <literal><#if big>...</#if></literal>即可。
- 而且 <literal><#if "${big}">...</#if></literal> 也是
- <emphasis>错误</emphasis>的,因为这样参数就是字符串类型了,
- 但是 <literal>if</literal> 指令的参数要求是布尔值,所以就会发生运行时错误。</para>
- </warning>
- </section>
-
- <section>
- <title>字符串插入指南:不要忘了转义!</title>
-
- <para>如果插值在 <link linkend="dgui_template_overallstructure"><phrase
- role="markedText">文本</phrase> 区</link> (也就是说,不在 <link
- linkend="dgui_template_exp_stringop_interpolation">字符串表达式</link> 中),如果 <link
- linkend="ref.directive.escape"><literal>escape</literal>
- 指令</link> 起作用了,那么将被插入的字符串会被自动转义。如果要生成HTML,
- 那么强烈建议你利用它来阻止跨站脚本攻击和非格式良好的HTML页面。这里有一个示例:</para>
-
- <programlisting role="template"><emphasis><#escape x as x?html></emphasis>
- ...
- <p>Title: <emphasis>${</emphasis>book.title<emphasis>}</emphasis></p>
- <p>Description: <emphasis><#noescape>${</emphasis>book.description<emphasis>}</#noescape></emphasis></p>
- <h2>Comments:</h2>
- <#list comments as comment>
- <div class="comment">
- <emphasis>${</emphasis>comment<emphasis>}</emphasis>
- </div>
- </#list>
- ...
-<emphasis></#escape></emphasis></programlisting>
-
- <para>这个示例展示了当生成HTML时,最好将完整的模板放入到
- <literal>escape</literal> 指令中。那么,如果
- <literal>book.title</literal> 包含 <literal>&</literal>,
- 在输出中它就会被替换成 <literal>&amp;</literal>,
- 而页面还会保持为格式良好的HTML。如果用户注释包含如
- <literal><iframe></literal>(或其它元素)的标记,那么就会被转义成
- <literal>&lt;iframe&gt;</literal> 的样子,使他们没有任何有害点。
- 但有时在数据模型中真的需要HTML,我们假设上面的
- <literal>book.description</literal> 在数据库中的存储是HTML格式的,
- 那么此时不得不使用 <literal>noescape</literal> 来抵消
- <literal>escape</literal> 的转义,不包含 <literal>escape</literal>,
- 模板就会像这样了:</para>
-
- <programlisting role="template"> ...
- <p>Title: ${book.title?html}</p>
- <p>Description: ${book.description}</p>
- <h2>Comments:</h2>
- <#list comments as comment>
- <div class="comment">
- ${comment?html}
- </div>
- </#list>
- ...</programlisting>
-
- <para>这和之前示例的效果是一样的, 但是这里可能会忘记
- <literal>?html</literal> 等内建函数,那么这就会有安全上的问题了。
- 在之前的示例中,你可能忘记 <literal>noescape</literal> 等内建函数,
- 也会造成不良的输出,但是起码是没有安全隐患的。</para>
- </section>
-
- <section>
- <title>数字插入指南</title>
-
- <para>如果表达式是数字类型,那么根据数字的默认格式,
- 数值将会转换成字符串。这也许会包含最大的小数,
- 数字分组和相似处理的问题。通常程序员应该设置默认的数字格式;
- 而模板设计者不需要处理它(但是可以使用 <literal>number_format</literal>
- 来设置;详情请参考 <link linkend="ref_directive_setting">
- <literal>setting</literal> 指令部分的文档</link>)。
- 可以使用<link linkend="ref_builtin_string_for_number">
- 内建函数 <literal>string</literal> </link>
- 为一个插值来重写默认数值格式。</para>
-
- <para>小数的分隔符通常(其他类似的符号也是这样,如分组符号)
- 是根据所在地的标准(语言,国家)来确定的,这也需要程序员来设置。例如这个模板:</para>
-
- <programlisting role="template">${1.5}</programlisting>
-
- <para>如果当前本地化设置为英语时,将会输出:</para>
-
- <programlisting role="output">1.5</programlisting>
-
- <para>而当前地区为德国时,将会输出:</para>
-
- <programlisting role="output">1,5</programlisting>
-
- <para>这是因为德国人使用逗号作为小数点。</para>
-
- <warning>
- <para>可以看出,插值的打印都是给用户看的(至少是这样的),
- 而不是给''计算机''的。有时候这样并不好,比如要打印数据库记录的主键,
- 用来作为URL中的一部分或HTML表单的隐藏域来作为提交内容,
- 或者要打印CSS/JavaScript中的数字,因为这些值都是给计算机程序去识别的而不是给用户看的。
- 很多程序对数字格式的要求非常严格,它们只能理解一部分简单的美式数字格式。那样的话,
- 可以使用内建函数<link linkend="ref_builtin_c"><literal>c</literal></link>
- (代表''计算机'')来解决这个问题,比如:</para>
-
- <programlisting role="template"><a href="/shop/productdetails?id=${product.id?c}">Details...</a></programlisting>
- </warning>
- </section>
-
- <section xml:id="dgui_template_valueinserion_universal_date">
- <title>日期/时间插入指南</title>
-
- <para>如果表达式的值是时间日期类型,那么日期中的数字将会按照默认格式来转换成文本。
- 通常程序员应该设置默认格式,而页面设计者无需处理这一点。(如果需要的话,
- <link linkend="topic.dateTimeFormatSettings">可以参考
- <literal>date_format</literal>, <literal>time_format</literal> 和
- <literal>datetime_format</literal> 的 </link>
- <link linkend="ref.directive.setting"><literal>setting</literal>
- 指令</link>设置)。当然,也可以使用
- <link linkend="ref_builtin_string_for_date">内建函数<literal>string</literal></link>
- 来覆盖单独插值的默认格式。</para>
-
- <warning>
- <para>为了将日期显示成文本,FreeMarker 必须知道日期中的哪一部分在使用,也就是说,
- 如果仅仅日期部分(年,月,日)使用或仅仅时间部分(时,分,秒,毫秒)使用或两部分都用。
- 不幸的是,由于Java平台技术的限制,自动探测一些变量是不现实的。
- 这时可以找程序员对数据模型中可能出问题的变量进行处理。
- 如果找出时间日期变量的哪部分在使用是不太可能的话,就必须帮助 FreeMarker 使用内建函数
- <link linkend="ref_builtin_date_datetype"><literal>date</literal>,
- <literal>time</literal> 和 <literal>datetime</literal></link> 来识别
- (比如 <literal>${lastUpdated?datetime}</literal> ),否则就会出现错误停止执行。</para>
- </warning>
- </section>
-
- <section>
- <title>布尔值插入指南</title>
-
- <para>若要使用插值方式来打印布尔值会引起错误,中止模板的执行。
- 例如: <literal>${a == 2}</literal> 就会引起错误,
- 它不会打印''true''或其他内容。这是因为没有全局来表示布尔值的好方法
- (有时想输出yes/no,但有时是想要enabled/disabled,on/off等等)。</para>
-
- <para>我们可以使用内建函数 <link
- linkend="ref_builtin_string_for_boolean"><literal>?string</literal></link>
- 来将布尔值转换为字符串形式。比如输出变量"married"的值(假设它是一个布尔值),
- 那么可以这么来写: <literal>${married?string("yes", "no")}</literal>。</para>
-
- <para>可以使用设置参数 <literal>boolean_format</literal>
- 来为 FreeMarker 配置默认的布尔值格式。那么,直接编写
- <literal>${married}</literal> 这样的代码就不会有问题了。但在很多应用程序中,
- 这样的做法是不推荐使用的,因为布尔值在不同的地方就应该呈现出不同的格式,
- 同时将格式留作默认值也可以认为是疏忽,因为这可能导致错误产生。</para>
-
- <para>当想生成JavaScript或其它计算机语言代码部分时,那么可以考虑使用
- <literal>${someBoolean?c}</literal>(<quote>c</quote> 代表计算机)来输出布尔值true/false。
- (请记住 <literal>?c</literal> 也可以用来输出给计算机看的数字。)</para>
- </section>
-
- <section>
- <title>精确的转换规则</title>
-
- <para>对于有兴趣研究的人,表达式的值转换为字符串(仍受限于转义)
- 精确的规则就是下面这些,以这个顺序进行:</para>
-
- <orderedlist>
- <listitem>
- <para>如果这个值是数字,那么它会按照指定的 <literal>number_format</literal>
- 设置规则来转换为字符串。所以这些转换通常是对用户进行的,而不是对计算机。</para>
- </listitem>
-
- <listitem>
- <para>如果这个值是日期,时间或时间日期类型的一种,那么它们会按照指定的
- <literal>date_format</literal>, <literal>time_format</literal>
- 或者 <literal>datetime_format</literal> 设置规则来转换为字符串。
- 如果它不能被探测出来是哪种日期类型(日期或时间或日期时间)时,就会发生错误了。</para>
- </listitem>
-
- <listitem>
- <para>如果值本来就是字符串类型的,不需要转换。</para>
- </listitem>
-
- <listitem>
- <para>如果 FreeMarker 引擎在传统兼容模式下:</para>
-
- <orderedlist>
- <listitem>
- <para>如果值是布尔类型,true值就转换成"true",false值将会转换为空字符串。</para>
- </listitem>
-
- <listitem>
- <para>如果表达式未被定义(<literal>null</literal> 或者变量未定义),
- 那么就转换为空字符串。</para>
- </listitem>
-
- <listitem>
- <para>否则就会发生错误中止模板执行。</para>
- </listitem>
- </orderedlist>
- </listitem>
-
- <listitem>
- <para>否则就会发生错误中止模板执行。</para>
- </listitem>
- </orderedlist>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="dgui_misc">
- <title>其它</title>
-
- <remark>Do we need a short chapter on i18n/charset issues in general,
- with the introduction of FreeMarker facilities on this field at the
- end?</remark>
-
- <section xml:id="dgui_misc_userdefdir">
- <title>自定义指令</title>
-
- <indexterm>
- <primary>macro</primary>
- </indexterm>
-
- <indexterm>
- <primary>transform</primary>
- </indexterm>
-
- <indexterm>
- <primary>custom directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>user-defined directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>directive</primary>
-
- <secondary>user-defined</secondary>
- </indexterm>
-
- <indexterm>
- <primary>tag</primary>
-
- <secondary>user-defined</secondary>
- </indexterm>
-
- <para>自定义指令可以使用 <literal>macro</literal> 指令来定义,
- 这是模板设计者所关心的内容。<phrase role="forProgrammers">
- Java程序员若不想在模板中实现定义指令,而是在Java语言中实现指令的定义,
- 这时可以使用 <literal>freemarker.template.TemplateDirectiveModel</literal> 类来扩展
- (请参考 <link linkend="pgui_datamodel_directive">后续章节</link>)。</phrase></para>
-
- <section>
- <title>基本内容</title>
-
- <indexterm>
- <primary>defining macro</primary>
- </indexterm>
-
- <para>宏是有一个变量名的模板片段。可以在模板中使用宏作为自定义指令,
- 这样就能进行重复性的工作。例如,创建一个宏变量来输出大字号的''Hello Joe!'':</para>
-
- <programlisting role="template"><emphasis><#macro greet></emphasis>
- <font size="+2">Hello Joe!</font>
-<emphasis></#macro></emphasis></programlisting>
-
- <para><literal>macro</literal> 指令自身不输出任何内容,
- 它只是用来创建宏变量,所以就会有一个名为 <literal>greet</literal>
- 的变量。在 <literal><#macro greet></literal> 和
- <literal></#macro></literal> 之间的内容
- (称为 <emphasis role="term">宏定义体</emphasis>)
- 将会在使用该变量作为指令时执行。可以在FTL标记中通过
- <literal>@</literal>代替<literal>#</literal>来使用自定义指令。
- 使用变量名作为指令名。而且,自定义指令的 <link
- linkend="gloss.endTag">结束标记</link> 也是需要的。那么,
- 就可以这样来使用 <literal>greet</literal>:</para>
-
- <programlisting role="template"><@greet></@greet></programlisting>
-
- <para>因为
- <literal><<replaceable>anything</replaceable>></<replaceable>anything</replaceable>></literal> 和
- <literal><<replaceable>anything</replaceable>/></literal> 是相同的,
- 也可以使用单标记形式(如果你了解 <link linkend="gloss.XML">XML</link>,那么就应该很熟悉了):</para>
-
- <programlisting role="template"><@greet/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <font size="+2">Hello Joe!</font>
- </programlisting>
-
- <para>宏能做的事情还有很多,因为在
- <literal><#macro <replaceable>...</replaceable>></literal>
- 和 <literal></#macro></literal> 之间的东西是模板片段,也就是说它可以包含插值
- (<literal>${<replaceable>...</replaceable>}</literal>) 和FTL标签
- (如 <literal><#if
- <replaceable>...</replaceable>><replaceable>...</replaceable></#if></literal>)。</para>
-
- <note>
- <para>程序员通常将使用
- <literal><@<replaceable>...</replaceable>></literal> 这称为
- 宏 <emphasis role="term">调用</emphasis>。</para>
- </note>
- </section>
-
- <section>
- <title>参数</title>
-
- <para>我们来改进 <literal>greet</literal> 宏使之可以使用任意的名字,
- 而不仅仅是''Joe''。为了实现这个目的,就要使用到
- <emphasis role="term">参数</emphasis>。在 <literal>macro</literal>
- 指令中,宏名称的后面位置是用来定义参数的。这里我们仅在
- <literal>greet</literal> 宏中定义一个参数,<literal>person</literal>:</para>
-
- <programlisting role="template"><#macro greet <emphasis>person</emphasis>>
- <font size="+2">Hello <emphasis>${person}</emphasis>!</font>
-</#macro></programlisting>
-
- <para>那么就可以这样来使用这个宏:</para>
-
- <programlisting role="template"><@greet <emphasis>person="Fred"</emphasis>/> and <@greet <emphasis>person="Batman"</emphasis>/>
-</programlisting>
-
- <para>这和HTML的语法是很相似的,将会输出:</para>
-
- <programlisting role="output"> <font size="+2">Hello <emphasis>Fred</emphasis>!</font>
- and <font size="+2">Hello <emphasis>Batman</emphasis>!</font>
- </programlisting>
-
- <para>那么我们就看到了,宏参数的真实值是可以作为变量
- (<literal>person</literal>)放在宏定义体中的。使用 <link
- linkend="gloss.predefinedDirective">预定义指令</link>时,参数的值
- (<literal>=</literal>号后边的值)可以是 <link
- linkend="dgui_template_exp">FTL 表达式</link>。
- 那么,不像HTML,<literal>"Fred"</literal>
- 和 <literal>"Batman"</literal> 引号就可以不用要了。
- <literal><@greet person=Fred/></literal> 也意味着使用变量的值
- <literal>Fred</literal> 作为 <literal>person</literal> 参数,
- 而不是字符串<literal>"Fred"</literal>。当然参数值并不一定是字符串类型,
- 也可以是数字,布尔值,哈希表,序列等。也可以在 <literal>=</literal>
- 号左边使用复杂表达式(比如 <literal>someParam=(price + 50)*1.25</literal>)。</para>
-
- <para>自定义指令可以有多个参数。如下所示,再添加一个新的参数 <literal>color</literal>:</para>
-
- <programlisting role="template"><#macro greet person <emphasis>color</emphasis>>
- <font size="+2" color="${color}">Hello ${person}!</font>
-</#macro></programlisting>
-
- <para>那么,这个宏就可以这样来使用:</para>
-
- <programlisting role="template"><@greet person="Fred" color="black"/></programlisting>
-
- <para>参数的顺序不重要,下面的这个和上面的含义也是相同的:</para>
-
- <programlisting role="template"><@greet color="black" person="Fred"/></programlisting>
-
- <para>当调用这个宏的时候,只能使用在 <literal>macro</literal>
- 指令中定义的参数(本例中是:<literal>person</literal> 和 <literal>color</literal>)。
- 那么当你尝试 <literal><@greet person="Fred" color="black" background="green"/></literal>
- 的时候就会发生错误,因为并没有在 <literal><#macro<replaceable>...</replaceable>></literal>
- 中提及参数 <literal>background</literal>。</para>
-
- <para>同时也必须给出在宏中定义所有参数的值。如果尝试
- <literal><@greet person="Fred"/></literal> 时也会发生错误,
- 因为忘记指定 <literal>color</literal>的值了。
- 很多情况下需要给一个参数指定一个相同的值,所以我们仅仅想在这个值发生变化后重新赋给变量。
- 那么要达到这个目的,在<literal>macro</literal>指令中必须这么来指定变量:
- <literal><replaceable>param_name</replaceable>=<replaceable>usual_value</replaceable></literal>。
- 例如,当没有特定值的时候,我们想要给 <literal>color</literal>
- 赋值为 <literal>"black"</literal>,那么 <literal>greet</literal>
- 指令就要这么来写:</para>
-
- <programlisting role="template"><#macro greet person color<emphasis>="black"</emphasis>>
- <font size="+2" color="${color}">Hello ${person}!</font>
-</#macro></programlisting>
-
- <para>现在,我们这么使用宏就可以了:
- <literal><@greet person="Fred"/></literal>,因为它和
- <literal><@greet person="Fred" color="black"/></literal> 是相等的,
- 这样参数 <literal>color</literal> 的值就是已知的了。
- 如果想给 <literal>color</literal> 设置为 <literal>"red"</literal>,
- 那么就写成: <literal><@greet person="Fred" color="red"/></literal>,
- 这时 <literal>macro</literal> 指令就会使用这个值来覆盖之前设置的通用值,
- 参数 <literal>color</literal> 的值就会是 <literal>"red"</literal> 了。</para>
-
- <para>根据已知的 <link linkend="dgui_template_exp">FTL 表达式规则</link>,
- 明白下面这一点是至关重要的。<literal>someParam=foo</literal>
- 和 <literal>someParam="${foo}"</literal> 是不同的。第一种情况,
- 是把变量 <literal>foo</literal> 的值作为参数的值来使用。第二种情况则是使用 <link
- linkend="dgui_template_exp_stringop_interpolation">插值形式的字符串</link>,
- 那么参数值就是字符串了,这个时候, <literal>foo</literal> 的值呈现为文本,
- 而不管 <literal>foo</literal> 是什么类型的(数字,日期等)。看下面这个例子:
- <literal>someParam=3/4</literal> 和 <literal>someParam="${3/4}"</literal> 是不同的。
- 如果指令需要 <literal>someParam</literal> 是一个数字值,
- 那么就不要用第二种方式。切记不要改变这些。</para>
-
- <para>宏参数的另外一个重要的方面是它们是局部变量。
- 更多局部变量的信息可以阅读:<xref linkend="dgui_misc_var"/></para>
- </section>
-
- <section>
- <title>嵌套内容</title>
-
- <para>自定义指令可以嵌套内容,和预定义指令相似:<literal><#if
- <replaceable>...</replaceable>><replaceable>nested
- content</replaceable></#if></literal>。
- 例如,下面这个例子中是创建了一个可以为嵌套的内容画出边框的宏:</para>
-
- <programlisting role="template"><#macro border>
- <table border=4 cellspacing=0 cellpadding=4><tr><td>
- <emphasis><#nested></emphasis>
- </tr></td></table>
-</#macro></programlisting>
-
- <para><literal><#nested></literal>
- 指令执行位于开始和结束标记指令之间的模板代码段。
- 如果这样写:</para>
-
- <programlisting role="template"><@border>The bordered text</@border></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <table border=4 cellspacing=0 cellpadding=4><tr><td>
- The bordered text
- </td></tr></table>
- </programlisting>
-
- <para><literal>nested</literal> 指令也可以多次被调用,例如:</para>
-
- <programlisting role="template"><#macro do_thrice><emphasis>
- <#nested>
- <#nested>
- <#nested></emphasis>
-</#macro>
-<@do_thrice>
- Anything.
-</@do_thrice></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Anything.
- Anything.
- Anything.</programlisting>
-
- <para>如果不使用 <literal>nested</literal> 指令,
- 那么嵌套的内容就不会被执行,如果不小心将
- <literal>greet</literal> 指令写成了这样:</para>
-
- <programlisting role="template"><@greet person="Joe">
- Anything.
-</@greet></programlisting>
-
- <para>FreeMarker 不会把它视为错误,只是输出:</para>
-
- <programlisting role="output"><font size="+2">Hello Joe!</font></programlisting>
-
- <para>嵌套的内容被忽略了,因为 <literal>greet</literal>
- 宏没有使用 <literal>nested</literal> 指令。</para>
-
- <para>嵌套的内容可以是任意有效的FTL,包含其他的用户自定义指令,这样也是对的:</para>
-
- <programlisting role="template"><@border>
- <ul>
- <@do_thrice>
- <li><@greet person="Joe"/>
- </@do_thrice>
- </ul>
-</@border></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <table border=4 cellspacing=0 cellpadding=4><tr><td>
- <ul>
- <li><font size="+2">Hello Joe!</font>
-
- <li><font size="+2">Hello Joe!</font>
-
- <li><font size="+2">Hello Joe!</font>
-
- </ul>
-
- </tr></td></table></programlisting>
-
- <para>在嵌套的内容中,宏的 <link linkend="dgui_misc_var">局部变量</link>
- 是不可见的。为了说明这点,我们来看:</para>
-
- <programlisting role="template"><#macro repeat count>
- <#local y = "test">
- <#list 1..count as x>
- ${y} ${count}/${x}: <#nested>
- </#list>
-</#macro>
-<@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}</@repeat></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> test 3/1: ? ? ?
- test 3/2: ? ? ?
- test 3/3: ? ? ?</programlisting>
-
- <para>因为 <literal>y</literal>, <literal>x</literal>
- 和 <literal>count</literal> 是宏的局部(私有)变量,从宏外部定义是不可见的。
- 此外不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱:</para>
-
- <programlisting role="template"><#macro test foo>${foo} (<#nested>) ${foo}</#macro>
-<@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>
-</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">A (B (C () C) B) A</programlisting>
- </section>
-
- <section xml:id="dgui_misc_userdefdir_loopvar">
- <title>宏和循环变量</title>
-
- <indexterm>
- <primary>loop variable</primary>
- </indexterm>
-
- <para>像 <literal>list</literal>这样的预定义指令可以使用循环变量;
- 可以阅读 <xref linkend="dgui_misc_var"/> 来理解循环变量。</para>
-
- <para>自定义指令也可以有循环变量。比如我们来扩展先前例子中的
- <literal>do_thrice</literal> 指令,就可以拿到当前的循环变量的值。
- 而对于预定义指令(如<literal>list</literal>),当调用指令时,循环变量的
- <emphasis>name</emphasis>是给定的(比如 <literal><#list foos as
- foo><replaceable>...</replaceable></#list></literal>
- 中的 <literal>foo</literal>),变量 <emphasis>value</emphasis>
- 的设置是由指令本身完成的。</para>
-
- <programlisting role="template"><#macro do_thrice>
- <#nested <emphasis>1</emphasis>>
- <#nested <emphasis>2</emphasis>>
- <#nested <emphasis>3</emphasis>>
-</#macro>
-<@do_thrice <emphasis>; x</emphasis>> <#-- user-defined directive uses ";" instead of "as" -->
- ${<emphasis>x</emphasis>} Anything.
-</@do_thrice></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> 1 Anything.
- 2 Anything.
- 3 Anything.
- </programlisting>
-
- <para>语法规则是给确定"循环"的循环变量传递真实值(比如重复嵌套内容)来作为
- <literal>nested</literal> 指令的参数(当然参数可以是任意的表达式)。
- 循环变量的名称是在自定义指令的开始标记(<literal><@...></literal>)
- 的参数后面通过分号确定的。</para>
-
- <para>一个宏可以使用多个循环变量(变量的顺序是很重要的):</para>
-
- <programlisting role="template"><#macro repeat count>
- <#list 1..count as x>
- <#nested <emphasis>x, x/2, x==count</emphasis>>
- </#list>
-</#macro>
-<@repeat count=4 ; <emphasis>c, halfc, last</emphasis>>
- ${<emphasis>c</emphasis>}. ${<emphasis>halfc</emphasis>}<#if <emphasis>last</emphasis>> Last!</#if>
-</@repeat></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> 1. 0.5
- 2. 1
- 3. 1.5
- 4. 2 Last!
- </programlisting>
-
- <para>在自定义指令的开始标签(分号之后)为循环变量指定不同的数字是没有问题的,
- 而不能在 <literal>nested</literal> 指令上使用。如果在分号之后指定的循环变量少,
- 那么就看不到 <literal>nested</literal>指令提供的最后的值,
- 因为没有循环变量来存储这些值,下面的这些都是可以的:</para>
-
- <programlisting role="template"><@repeat count=4 ; <emphasis>c, halfc, last</emphasis>>
- ${c}. ${halfc}<#if last> Last!</#if>
-</@repeat>
-<@repeat count=4 ; <emphasis>c, halfc</emphasis>>
- ${c}. ${halfc}
-</@repeat>
-<@repeat count=4>
- Just repeat it...
-</@repeat></programlisting>
-
- <para>如果在分号后面指定了比 <literal>nested</literal> 指令还多的变量,
- 那么最后的循环变量将不会被创建(在嵌套内容中不会被定义)。</para>
- </section>
-
- <section>
- <title>自定义指令和宏进阶</title>
-
- <para>现在你也许已经阅读过 FreeMarker 参考手册的相关部分了:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="ref.directive.userDefined">调用自定义指令</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.macro"><literal>macro</literal>
- 指令</link></para>
- </listitem>
- </itemizedlist>
-
- <para>也可以在FTL中定义方法,参见 <link
- linkend="ref.directive.function"> <literal>function</literal> 指令</link>。</para>
-
- <para>也许你对命名空间感兴趣: <xref linkend="dgui_misc_namespace"/>。
- 命名空间可以帮助你组织和重用经常使用的宏。</para>
- </section>
- </section>
-
- <section xml:id="dgui_misc_var">
- <title>在模板中定义变量</title>
-
- <indexterm>
- <primary>variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>loop variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>local variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>temporary variable</primary>
- </indexterm>
-
- <para>正如我们已经描述过的,模板可以使用在数据模型中定义的变量。
- 在数据模型之外,模板本身也可以定义变量来使用。
- 这些临时变量可以使用FTL指令来创建和替换。请注意每一次的 <link
- linkend="gloss.templateProcessingJob">模板执行</link> 工作都维护它自己的私有变量,
- 同时来渲染页面。变量的初始值是空,当模板执行工作结束这些变量便被销毁了。</para>
-
- <para>可以访问一个在模板里定义的变量,就像是访问数据模型根root上的变量一样。
- 这个变量比定义在数据模型中的同名参数有更高的优先级,也就是说,
- 如果恰巧定义了一个名为''foo''的变量,而在数据模型中也有一个名为''foo''的变量,
- 那么模板中的变量就会将数据模型根上的变量隐藏(而不是覆盖!)。
- 例如,<literal>${foo}</literal> 将会输出在模板中定义的变量。</para>
-
- <para>在模板中可以定义三种类型的变量:</para>
-
- <itemizedlist>
- <listitem>
- <para><emphasis role="term">''简单''变量</emphasis>:
- 它能从模板中的任何位置来访问,或者从使用 <literal>include</literal>
- 指令引入的模板访问。可以使用 <link
- linkend="ref.directive.assign"><literal>assign</literal></link>
- 指令来创建或替换这些变量。因为宏和方法只是变量,那么 <link
- linkend="ref.directive.macro"><literal>macro</literal> 指令 </link>
- 和 <link linkend="ref.directive.function"><literal>function</literal> 指令</link>
- 也可以用来设置变量,就像 <literal>assign</literal> 那样。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">局部变量</emphasis>:它们只能被设置在
- <link linkend="gloss.macroDefinitionBody">宏定义体</link>内,
- 而且只在宏内可见。一个局部变量的生命周期只是宏的调用过程。可以使用
- <link linkend="ref.directive.local"><literal>local</literal>指令</link>
- 在宏定义体内创建或替换局部变量。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">循环变量</emphasis>:循环变量是由如
- <link linkend="ref.directive.list"><literal>list</literal></link>
- 指令自动创建的,而且它们只在指令的开始和结束标记内有效。<link
- linkend="ref.directive.macro">宏</link> 的参数是局部变量而不是循环变量。</para>
- </listitem>
-
- <listitem>
- <para><emphasis role="term">全局变量</emphasis>:这是一个高级话题了,
- 并且这种变量最好别用。即便它们属于不同的命名空间,
- 全局变量也被所有模板共享,因为它们是被 <link
- linkend="ref.directive.import"><literal>import</literal>进来的</link>,
- 不同于 <literal>include</literal> 进来的。那么它们的可见度就像数据模型那样。
- 全局变量通过 <link
- linkend="ref.directive.global"><literal>global</literal>指令</link>来定义。</para>
- </listitem>
- </itemizedlist>
-
- <para>示例:使用 <literal>assign</literal> 创建和替换变量:</para>
-
- <programlisting role="template"><#assign x = 1> <#-- create variable x -->
-${x}
-<#assign x = x + 3> <#-- replace variable x -->
-${x}</programlisting>
-
- <para>输出为:</para>
-
- <programlisting role="output">1
-4</programlisting>
-
- <para>局部变量也会隐藏(不是覆盖)同名的''简单''变量。
- 循环变量也会隐藏(不是覆盖)同名的''简单''变量。例如:</para>
-
- <programlisting role="template"><#assign x = "plain">
-1. ${x} <#-- we see the plain var. here -->
-<@test/>
-6. ${x} <#-- the value of plain var. was not changed -->
-<#list ["loop"] as x>
- 7. ${x} <#-- now the loop var. hides the plain var. -->
- <#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
- 8. ${x} <#-- it still hides the plain var. -->
-</#list>
-9. ${x} <#-- the new value of plain var. -->
-
-<#macro test>
- 2. ${x} <#-- we still see the plain var. here -->
- <#local x = "local">
- 3. ${x} <#-- now the local var. hides it -->
- <#list ["loop"] as x>
- 4. ${x} <#-- now the loop var. hides the local var. -->
- </#list>
- 5. ${x} <#-- now we see the local var. again -->
-</#macro></programlisting>
-
- <para>输出为:</para>
-
- <programlisting role="output">1. plain
- 2. plain
- 3. local
- 4. loop
- 5. local
-6. plain
- 7. loop
- 8. loop
-9. plain2
- </programlisting>
-
- <para>内部循环变量可以隐藏外部循环变量:</para>
-
- <programlisting role="template"><#list ["loop 1"] as x>
- ${x}
- <#list ["loop 2"] as x>
- ${x}
- <#list ["loop 3"] as x>
- ${x}
- </#list>
- ${x}
- </#list>
- ${x}
-</#list></programlisting>
-
- <para>输出为:</para>
-
- <programlisting role="output"> loop 1
- loop 2
- loop 3
- loop 2
- loop 1</programlisting>
-
- <para>请注意,循环变量的设置是通过指令调用时创建的(本例中的
- <literal><list <replaceable>...</replaceable>></literal> 标签)。
- 没有其他的方式去改变循环变量的值(也就是说,不能使用定义指令来改变它的值)。
- 从上面的示例来看,也可以使用一个循环变量来暂时隐藏另外一个。</para>
-
- <para>有时会发生一个变量隐藏数据模型中的同名变量,
- 但是如果想访问数据模型中的变量,此时就可以使用 <link
- linkend="dgui_template_exp_var_special">特殊变量</link>
- <literal>globals</literal>。例如,假设我们在数据模型中有一个名为
- <literal>user</literal> 的变量,值为''Big Joe'':</para>
-
- <programlisting role="template"><#assign user = "Joe Hider">
-${user} <#-- prints: Joe Hider -->
-${.globals.user} <#-- prints: Big Joe --></programlisting>
-
- <para>通过 <link linkend="ref.directive.global"><literal>global</literal>指令</link>
- 设置的变量可以隐藏数据模型中的同名变量。通常,全局变量的设置会有精确的目的。
- 但仍然可以使用如下方式来访问数据模型变量:<literal>.data_model.user</literal>。</para>
-
- <para>想了解更多的变量使用语法,请阅读: <xref linkend="dgui_template_exp"/></para>
- </section>
-
- <section xml:id="dgui_misc_namespace">
- <title>命名空间</title>
-
- <indexterm>
- <primary>namespaces</primary>
- </indexterm>
-
- <indexterm>
- <primary>libraries</primary>
- </indexterm>
-
- <para>当运行FTL模板时,就会有使用 <literal>assign</literal>
- 和 <literal>macro</literal> 指令创建的变量的集合(可能是空的),
- 可以从 <link linkend="dgui_misc_var">前一章</link> 来看如何使用它们。
- 像这样的变量集合被称为 <emphasis role="term">命名空间</emphasis>。
- 简单的情况下可以只使用一个命名空间,称之为
- <emphasis role="term">主命名空间</emphasis>。因为通常只使用该命名空间,
- 所以就没有意识到这点。</para>
-
- <para>如果想创建可以重复使用的宏,函数和其他变量的集合,
- 通常用术语来说就是引用 <emphasis role="term">库</emphasis>。
- 使用多个命名空间是必然的。只要考虑你在一些项目中,
- 或者想和他人共享使用的时候,你是否有一个很大的宏的集合。
- 但要确保库中没有宏(或其他变量)名和数据模型中变量同名,
- 而且也不能和模板中引用其他库中的变量同名是不可能的。
- 通常来说,变量因为名称冲突时也会相互冲突。
- 所以要为每个库中的变量使用不同的命名空间。</para>
-
- <section>
- <title>创建一个库</title>
-
- <para>我们来建立一个简单的库。假设你需要通用的变量
- <literal>copyright</literal> 和 <literal>mail</literal>
- (在你有疑问之前,宏 <emphasis>当作是</emphasis> 变量):</para>
-
- <programlisting role="template"><#macro copyright date>
- <p>Copyright (C) ${date} Julia Smith. All rights reserved.</p>
-</#macro>
-
-<#assign mail = "jsmith@acme.com"></programlisting>
-
- <para>把上面的这些定义存储在文件 <literal>lib/my_test.ftl</literal> 中
- (目录是存放模板的位置)。假设想在 <literal>aWebPage.ftl</literal>
- 中使用这个模板。如果在 <literal>aWebPage.ftl</literal> 中使用
- <literal><#include "/lib/my_test.ftl"></literal>,
- 那么就会在主命名空间中创建两个变量,这样就不是很好,
- 因为想让它们只在同一个命名空间''My Test Library''中。所以就不得不使用 <link
- linkend="ref.directive.import"><literal>import</literal>指令</link>
- 来代替 <literal>include</literal> 了。乍一看,这个指令和
- <literal>include</literal> 很相似,但是它会为 <literal>lib/my_test.ftl</literal>
- 创建一个空的命名空间,然后在那里执行。<literal>lib/my_test.ftl</literal>
- 会发现它自己在一个新的环境中,那里只有数据模型的变量可以找到
- (因为它们在哪儿都是可见的),然后会在这个环境中创建两个变量。现在来看这很不错,
- 但是如果想访问 <literal>aWebPage.ftl</literal> 中的两个变量,
- 而它们使用的是主命名空间,就不能看到其他命名空间中的变量。
- 解决方法是 <literal>import</literal> 指令不仅仅创建命名空间,而且要通过
- <literal>import</literal> 的调用者(本例中的主命名空间)创建一个新的哈希表变量,
- 这就成为进入新的命名空间的大门。那么
- <literal>aWebPage.ftl</literal> 就像下面这样:</para>
-
- <programlisting role="template"><#import "/lib/my_test.ftl" as <emphasis>my</emphasis>> <#-- the hash called "my" will be the "gate" -->
-<@<emphasis>my</emphasis>.copyright date="1999-2002"/>
-${<emphasis>my</emphasis>.mail}</programlisting>
-
- <para>要注意它是怎么访问为 <literal>/lib/my_test.ftl</literal>
- 创建的命名空间中的变量的,使用新创建的命名空间访问哈希表,
- <literal>my</literal>。将会输出:</para>
-
- <programlisting role="output"> <p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.</p>
-jsmith@acme.com</programlisting>
-
- <para>如果在主命名空间中有一个变量,名为 <literal>mail</literal>
- 或 <literal>copyright</literal>,那么就不会引起混乱了,
- 因为两个模板使用了不同的命名空间。例如,在 <literal>lib/my_test.ftl</literal>
- 中修改 <literal>copyright</literal> 成如下这样:</para>
-
- <programlisting role="template"><#macro copyright date>
- <p>Copyright (C) ${date} Julia Smith. All rights reserved.
- <br>Email: <emphasis>${mail}</emphasis></p>
-</#macro></programlisting>
-
- <para>然后替换 <literal>aWebPage.ftl</literal> 中的内容:</para>
-
- <programlisting role="template"><#import "/lib/my_test.ftl" as my>
-<emphasis><#assign mail="fred@acme.com"></emphasis>
-<@my.copyright date="1999-2002"/>
-${my.mail}
-${mail}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
- <br>Email: <emphasis>jsmith@acme.com</emphasis></p>
-jsmith@acme.com
-fred@acme.com</programlisting>
-
- <para>当调用了 <literal>copyright</literal> 宏之后,输出和上面的是相似的,
- 因为 FreeMarker 已经暂时转向由 <literal>import</literal> 指令为
- <literal>/lib/my_test.ftl</literal> 生成的命名空间了。因此,
- <literal>copyright</literal> 宏看到这里存在的变量 <literal>mail</literal>,
- 而不是主命名空间中存在的其它 <literal>mail</literal></para>
- </section>
-
- <section>
- <title>在引入的命名空间中编写变量</title>
-
- <para>偶尔想要在一个被包含的命名空间上创建或替换一个变量。
- 那么可以使用 <literal>assign</literal> 指令,
- 如果用到了它的 <literal>namespace</literal> 变量,例如下面这样:</para>
-
- <programlisting role="template"><#import "/lib/my_test.ftl" as my>
-${my.mail}
-<#assign mail="jsmith@other.com" <emphasis>in my</emphasis>>
-${my.mail}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">jsmith@acme.com
-jsmith@other.com</programlisting>
- </section>
-
- <section>
- <title>命名空间和数据模型</title>
-
- <para>数据模型中的变量在任何位置都是可见的。例如,
- 如果在数据模型中有一个名为 <literal>user</literal>
- 的变量,那么 <literal>lib/my_test.ftl</literal> 也能访问它,
- <literal>aWebPage.ftl</literal> 当然也能:</para>
-
- <programlisting role="template"><#macro copyright date>
- <p>Copyright (C) ${date} <emphasis>${user}</emphasis>. All rights reserved.</p>
-</#macro>
-
-<#assign mail = "<emphasis>${user}</emphasis>@acme.com"></programlisting>
-
- <para>如果 <literal>user</literal> 是 ''Fred''的话,下面这个例子:</para>
-
- <programlisting role="template"><#import "/lib/my_test.ftl" as my>
-<@my.copyright date="1999-2002"/>
-${my.mail}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
-Fred@acme.com</programlisting>
-
- <para>不要忘了在模板的命名空间(可以使用 <literal>assign</literal>
- 或 <literal>macro</literal> 指令来创建的变量)
- 中的变量有着比数据模型中的变量更高的优先级。因此,
- 数据模型的内容不会干涉到由库创建的变量。</para>
-
- <note>
- <para>在一些不寻常的应用中,也许想在模板中创建所有命名空间都可见的变量,
- 就像数据模型中的变量一样。但是你不能在模板中改变数据模型,
- 却可以通过 <literal>global</literal> 指令来达到相似的效果,
- 可以阅读 <link linkend="ref.directive.global">参考手册</link> 来获得更多信息。</para>
- </note>
- </section>
-
- <section>
- <title>命名空间的生命周期</title>
-
- <para>命名空间由使用 <literal>import</literal> 指令中所写的路径来识别。
- 如果想多次 <literal>import</literal> 这个路径,那么只会为第一次
- <literal>import</literal> 引用创建命名空间并执行模板。后面相同路径的
- <literal>import</literal> 只是创建一个哈希表当作访问相同命名空间的“门”。
- 例如,在 <literal>aWebPage.ftl</literal> 中:</para>
-
- <programlisting role="template"><#import "/lib/my_test.ftl" as my>
-<#import "/lib/my_test.ftl" as foo>
-<#import "/lib/my_test.ftl" as bar>
-${my.mail}, ${foo.mail}, ${bar.mail}
-<#assign mail="jsmith@other.com" in my>
-${my.mail}, ${foo.mail}, ${bar.mail}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">jsmith@acme.com, jsmith@acme.com, jsmith@acme.com
-jsmith@other.com, jsmith@other.com, jsmith@other.com</programlisting>
-
- <para>这里可以看到通过 <literal>my</literal>, <literal>foo</literal>
- 和 <literal>bar</literal> 访问相同的命名空间。</para>
-
- <para>请注意,命名空间是不分层次的,它们相互之间是独立存在的。
- 那么,如果在命名空间N1中 <literal>import</literal> 命名空间N2,
- 那N2也不在N1中,N1只是可以通过哈希表来访问N2。这和在主命名空间中
- <literal>import</literal> N2,然后直接访问命名空间N2是一样的过程。</para>
-
- <para>每一次 <link linkend="gloss.templateProcessingJob">模板的执行过程</link>,
- 它都有一个私有的命名空间的集合。每一次模板执行工作都是一个分离且有序的过程,
- 它们仅仅存在一段很短的时间,同时页面用以渲染内容,
- 然后就和所有填充过的命名空间一起消失了。因此,无论何时我们说第一次调用
- <literal>import</literal>,一个单一模板执行工作的内容都是这样。</para>
- </section>
-
- <section>
- <title>为他人编写库</title>
-
- <indexterm>
- <primary>library path</primary>
- </indexterm>
-
- <para>如果你已经为其他人员编写一个有用的,高质量的库,你也许想把它放在网络上
- (就像 <link
- xlink:href="https://freemarker.apache.org/libraries.html">https://freemarker.apache.org/libraries.html</link>)。
- 为了防止和其他作者使用库的命名相冲突,而且引入其他库时要书写简单,
- 这有一个事实上的标准,那就是指定库路径的格式。这个标准是:
- 库的路径必须对模板和其他库可用(可引用),就像这样:</para>
-
- <para><literal>/lib/<replaceable>yourcompany.com</replaceable>/<replaceable>your_library</replaceable>.ftl</literal></para>
-
- <para>如果你为Example公司工作,它们拥有www.example.com网的主页,
- 你的工作是开发一个部件库,那么要引入你所写的FTL的路径应该是:</para>
-
- <para><literal>/lib/example.com/widget.ftl</literal></para>
-
- <para>请注意,www已经被省略了。第三次路径分割后的部分可以包含子目录,可以像下面这样写:</para>
-
- <para><literal>/lib/example.com/commons/string.ftl</literal></para>
-
- <para>一个重要的规则就是路径不应该包含大写字母,为了分隔词语,
- 使用下划线 <literal>_</literal>,就像 <literal>wml_form</literal>
- (而不是 <literal>wmlForm</literal> )。</para>
-
- <para>请注意,如果你的工作不是为公司或组织开发库,你应该使用项目主页的URL,比如
- <literal>/lib/example.sourceforge.net/example.ftl</literal>,或
- <literal>/lib/geocities.com/jsmith/example.ftl</literal>。</para>
- </section>
- </section>
-
- <section xml:id="dgui_misc_whitespace">
- <title>空白处理</title>
-
- <indexterm>
- <primary>white-space removal</primary>
- </indexterm>
-
- <para>在运行中,模板中的 <link linkend="gloss.whiteSpace">空白</link>
- 处理在某种程度上来说是纠缠所有模板引擎的一个问题。</para>
-
- <para>我们来看这个模板。我已经用颜色标记了模板中的组件:
- <phrase role="markedText">文本</phrase>,
- <phrase role="markedInterpolation">插值</phrase>,
- <phrase role="markedFTLTag">FTL 标签</phrase>.。
- 使用 <phrase role="markedInvisibleText">[BR]</phrase> 来想象
- <link linkend="gloss.lineBreak">换行</link>。</para>
-
- <programlisting role="template"><phrase role="markedText"><p>List of users:<phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"><#assign users = [{"name":"Joe", "hidden":false},<phrase
- role="markedInvisibleText">[BR]</phrase>
- {"name":"James Bond", "hidden":true},<phrase
- role="markedInvisibleText">[BR]</phrase>
- {"name":"Julia", "hidden":false}]></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<ul><phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"><#list users as user></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedFTLTag"><#if !user.hidden></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <li><phrase role="markedInterpolation">${user.name}</phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedFTLTag"></#if></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"></#list></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-</ul><phrase role="markedInvisibleText">[BR]</phrase>
-<p>That's all.</phrase></programlisting>
-
- <para>如果 FreeMarker 能按照规则输出所有的
- <phrase role="markedText">文本</phrase>,那将会输出:</para>
-
- <programlisting role="output"><phrase role="markedText"><p>List of users:<phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInvisibleText">[BR]</phrase>
-<ul><phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedInvisibleText">[BR]</phrase>
- <li></phrase>Joe<phrase role="markedText"><phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedInvisibleText">[BR]</phrase>
- <li></phrase>Julia<phrase role="markedText"><phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedInvisibleText">[BR]</phrase>
-</ul><phrase role="markedInvisibleText">[BR]</phrase>
-<p>That's all.</phrase></programlisting>
-
- <para>这里有太多的不想要的空格和换行了。幸运的是,HTML和XML对空白都不是敏感的,
- 但是这么多多余的空白是很令人头疼的,而且处理后的HTML文件大小增加也是没必要的。
- 当然,对于空白敏感方式的输出这依旧是个大问题。</para>
-
- <para>FreeMarker 提供下面的工具来处理这个问题:</para>
-
- <itemizedlist>
- <listitem>
- <para>忽略某些模板文件的空白的工具
- <phrase role="forProgrammers">(解析阶段空白就被移除了)</phrase>:</para>
-
- <itemizedlist>
- <listitem>
- <para>剥离空白:这个特性会自动忽略在FTL标签周围多余的空白。
- 这个特性可以通过模板来随时启用和禁用。</para>
- </listitem>
-
- <listitem>
- <para>微调指令:<literal>t</literal>,<literal>rt</literal>,
- <literal>lt</literal>。使用这些指令可以明确地告诉 FreeMarker 去忽略某些空白。
- 可以阅读 <link linkend="ref.directive.t">参考手册</link> 来获取更多信息。</para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.ftl"><literal>ftl</literal></link> 参数
- <literal>strip_text</literal>:这将从模板中删除所有顶级文本。
- 对于只包含定义宏的模板来说很有用(还有其它一些没有输出的指令),
- 因为它可以移除宏定义和其他顶级指令中的换行符,
- 这样可以提高模板的可读性。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>从输出中移除空白的工具
- <phrase role="forProgrammers">(移除临近的空白)</phrase>:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>compress</literal> 指令。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <section xml:id="dgui_misc_whitespace_stripping">
- <title>剥离空白</title>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>stripping</secondary>
- </indexterm>
-
- <para>如果对模板开启这个特性,那么它就会自动忽略
- (也就是不在输出中打印出来)两种典型的多余空白:</para>
-
- <itemizedlist>
- <listitem>
- <para>缩进空白和在行末尾的尾部空白(包括换行符)在只包含FTL标签的行中会被忽略
- (比如 <literal><@myMacro/></literal>, <literal><#if
- <replaceable>...</replaceable>></literal>)和FTL注释(如 <literal><#-- blah --></literal>),
- 除了被忽略的空白本身。例如,如果一行只包含一个<literal><#if
- <replaceable>...</replaceable>></literal>,
- 那么在标签前面的缩进和标签后面的换行符将会被忽略。
- 但是,如果这行上包含<literal><#if <replaceable>...</replaceable>>x</literal>,
- 那么空白就不会被忽略,因为这个<literal>x</literal>不是FTL标签。请注意,根据这些规则,
- 一行上包含<literal><#if <replaceable>...</replaceable>><#list
- <replaceable>...</replaceable>></literal>,空白就会被忽略,
- 而一行上有<literal><#if <replaceable>...</replaceable>> <#list
- <replaceable>...</replaceable>></literal>这样的就不会,
- 因为在两个FTL标签之间的空白是嵌入的空白,而不是缩进的或尾部空白。</para>
- </listitem>
-
- <listitem>
- <para>加在下面这些指令之间的空白会被忽略:<literal>macro</literal>,
- <literal>function</literal>,<literal>assign</literal>,
- <literal>global</literal>,<literal>local</literal>,
- <literal>ftl</literal>, <literal>import</literal>,
- 但也是指令之间<emphasis>仅仅</emphasis>只有一个空白或FTL注释。实际应用中,
- 它意味着你可以在宏定义和参数定义之间放置空行,因为行间距是为了更好的可读性,
- 不包括在输出中打印不必要的空行(换行符)。</para>
- </listitem>
- </itemizedlist>
-
- <para>使用了剥离空白后,上面那个例子中的输出就会是:</para>
-
- <programlisting role="output"><phrase role="markedText"><p>List of users:<phrase
- role="markedInvisibleText">[BR]</phrase>
-<ul><phrase role="markedInvisibleText">[BR]</phrase>
- <li></phrase>Joe<phrase role="markedText"><phrase
- role="markedInvisibleText">[BR]</phrase>
- <li></phrase>Julia<phrase role="markedText"><phrase
- role="markedInvisibleText">[BR]</phrase>
-</ul><phrase role="markedInvisibleText">[BR]</phrase>
-<p>That's all.</phrase></programlisting>
-
- <para>这是因为在剥离之后,模板就变成这样了,被忽略的文本没有被<phrase
- role="markedText">标色</phrase>:</para>
-
- <programlisting role="template"><phrase role="markedText"><p>List of users:<phrase
- role="markedInvisibleText">[BR]</phrase></phrase>
-<phrase role="markedFTLTag"><#assign users = [{"name":"Joe", "hidden":false},<phrase
- role="markedInvisibleText">[BR]</phrase>
- {"name":"James Bond", "hidden":true},<phrase
- role="markedInvisibleText">[BR]</phrase>
- {"name":"Julia", "hidden":false}]></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedText"><ul><phrase role="markedInvisibleText">[BR]</phrase></phrase>
-<phrase role="markedFTLTag"><#list users as user></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
- <phrase role="markedFTLTag"><#if !user.hidden></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedText"> <li><phrase role="markedInterpolation">${user.name}</phrase><phrase
- role="markedInvisibleText">[BR]</phrase></phrase>
- <phrase role="markedFTLTag"></#if></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedFTLTag"></#list></phrase><phrase
- role="markedInvisibleText">[BR]</phrase>
-<phrase role="markedText"></ul><phrase role="markedInvisibleText">[BR]</phrase>
-<p>That's all.</phrase></programlisting>
-
- <para>剥离空白功能可以通过 <link
- linkend="ref.directive.ftl"><literal>ftl</literal> 指令</link>
- 在模板中开启或关闭。如果你没有通过 <literal>ftl</literal> 指令来指定,
- 那么剥离空白功能是开启还是关闭就依据程序员是如何设置 FreeMarker 的了。
- 默认的情况下剥离空白是开启的,程序员可以留着不管(<phrase
- role="forProgrammers">建议这样做</phrase>)。<phrase
- role="forProgrammers">请注意开启剥离空白时 <emphasis>不会</emphasis>
- 降低模板执行的效率,剥离空白的操作在模板加载时就已经完成了。</phrase></para>
-
- <para>剥离空白可以为单独的一行关闭,就是使用<link
- linkend="ref.directive.nt"><literal>nt</literal></link>
- 指令(对没有去掉空白的行来说)。</para>
- </section>
-
- <section>
- <title>使用compress指令</title>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>compress</secondary>
- </indexterm>
-
- <para>另外一种方法就是使用<link
- linkend="ref.directive.compress"><literal>compress</literal>
- 指令</link>。和剥离空白相反,这个工作是直接基于生成的输出内容,
- 而不是对于模板进行。也就是说,它会动态地检查输出内容,
- 而不会检查生成输出FTL的程序。它会很强势地移除缩进,
- 空行和重复的空格/制表符(可以阅读<link
- linkend="ref.directive.compress">参考手册</link>
- 部分来获取更多信息)。所以对于下面这段代码:</para>
-
- <programlisting role="template"><emphasis><#compress></emphasis>
-<#assign users = [{"name":"Joe", "hidden":false},
- {"name":"James Bond", "hidden":true},
- {"name":"Julia", "hidden":false}]>
-List of users:
-<#list users as user>
- <#if !user.hidden>
- - ${user.name}
- </#if>
-</#list>
-That's all.
-<emphasis></#compress></emphasis></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">List of users:
-- Joe
-- Julia
-That's all.</programlisting>
-
- <para>请注意 <literal>compress</literal> 是完全独立于剥离空白特性的。
- 所以它剥离模板中的空白是可能的,那么之后输出的内容就是被
- <literal>压缩</literal> 过的。</para>
-
- <para>此外,在默认情况下,名为 <literal>compress</literal>
- 的用户自定义指令是可以在数据模型中存在的(由于向下兼容特性)。
- 这和指令是相同的,除了可以选择设置 <literal>single_line</literal> 参数,
- 这将会移除所有的介于其中的换行符。在最后那个例子中,如果使用<literal><@compress
- single_line=true><replaceable>...</replaceable></@compress></literal>
- 来代替<literal><#compress><replaceable>...</replaceable></#compress></literal>,
- 那么就会得到如下输出:</para>
-
- <programlisting role="output">List of users: - Joe - Julia That's all.</programlisting>
- </section>
- </section>
-
- <section xml:id="dgui_misc_alternativesyntax">
- <title>替换(方括号)语法</title>
-
- <indexterm>
- <primary>alternative syntax</primary>
- </indexterm>
-
- <indexterm>
- <primary>square bracket syntax</primary>
- </indexterm>
-
- <note>
- <para>这个特性从 FreeMarker 2.3.4 版本后才存在。</para>
- </note>
-
- <para>FreeMarker支持一个替换的语法。就是在FreeMarker的指令和注释中用
- <literal>[</literal> 和 <literal>]</literal> 来代替
- <literal><</literal> 和 <literal>></literal>,例如下面这个例子:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>调用预定义指令:<literal>[#list animals as
- animal]<replaceable>...</replaceable>[/#list]</literal></para>
- </listitem>
-
- <listitem>
- <para>调用自定义指令:<literal>[@myMacro
- /]</literal></para>
- </listitem>
-
- <listitem>
- <para>注释:<literal>[#-- the comment --]</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>为了使用这种语法从而代替默认语法,从模板开始,使用 <link
- linkend="ref_directive_ftl"><literal>ftl</literal> 指令</link> 都要使用这用语法。
- 如果你不知道什么是 <literal>ftl</literal> 指令,那么就用
- <literal>[#ftl]</literal> 来开始模板,要记住这个要放在文件的最前面(除了<link
- linkend="gloss.whiteSpace">white-space</link>可以在它前面)。
- 例如,下面的示例是 <link linkend="dgui_quickstart_template">入门</link>
- 章节的最后一个例子使用这种替换语法的样子(假设这是一个完整的模板,而不是一个片段):</para>
-
- <programlisting role="template"><emphasis>[#ftl]</emphasis>
-<p>We have these animals:
-<table border=1>
- <tr><th>Name<th>Price
- <emphasis>[#list animals as animal]</emphasis>
- <tr>
- <td>
- <emphasis>[#if animal.size == "large"]</emphasis><b><emphasis>[/#if]</emphasis>
- ${animal.name}
- <emphasis>[#if animal.size == "large"]</emphasis></b><emphasis>[/#if]</emphasis>
- <td>${animal.price} Euros
- <emphasis>[/#list]</emphasis>
-</table></programlisting>
-
- <para>这种替换语法(方括号)和默认语法(尖括号)在一个模板中是相互排斥的。
- 那就是说,整个模板要么全部使用替换语法,要么全部使用默认语法。
- 如果模板使用了替换语法,那么如 <literal><#if <replaceable>...</replaceable>></literal>
- 这样的部分就会被算作是静态文本,而不是FTL标签了。类似地,如果模板使用默认语法,
- 那么如 <literal>[#if <replaceable>...</replaceable>]</literal> 这样的也会被算作是静态文本,
- 而不是FTL标签。</para>
-
- <para>如果你以<literal>[#ftl <replaceable>...</replaceable>]</literal>
- (<literal><replaceable>...</replaceable></literal> 代表可选的参数列表,
- 当然仅用 <literal>[#ftl]</literal> 也行)来开始文件,那文件就会使用替换(方括号)语法。
- 如果使用 <literal><#ftl <replaceable>...</replaceable>></literal> 来开始,
- 那么文件就会使用正常(尖括号)语法。如果文件中没有 <literal>ftl</literal> 指令,
- 那么程序员可以通过配置FreeMarker <phrase role="forProgrammers">
- (程序员可以参看 javadoc API 文档中的
- <literal>Configuration.setTagSyntax(int)</literal>)</phrase> 来决定使用哪种语法。
- 但是大多数情况,程序员可能使用默认配置。FreeMarker 2.3.x 版本默认配置使用常规语法。
- 而2.4版本中的默认配置将会自动检测,也就是说第一个FreeMarker标签决定了语法形式
- (它可以是任意的,而不仅仅是<literal>ftl</literal>)。</para>
- </section>
- </chapter>
- </part>
-
- <part xml:id="pgui">
- <title>程序开发指南</title>
-
- <chapter xml:id="pgui_quickstart">
- <title>入门</title>
-
- <para>如果你还是使用FreeMarker的新手,在开始本章之前,
- 你应该先阅读 <xref linkend="dgui_quickstart_basics"/>。</para>
-
- <section xml:id="pgui_quickstart_createconfiguration">
- <title>创建 Configuration 实例</title>
-
- <indexterm>
- <primary>configuration</primary>
- </indexterm>
-
- <para>首先,你应该创建一个 <literal>freemarker.template.Configuration</literal> 实例,
- 然后调整它的设置。<literal>Configuration</literal>
- 实例是存储 FreeMarker 应用级设置的核心部分。同时,它也处理创建和
- <emphasis>缓存</emphasis> 预解析模板(比如 <literal>Template</literal> 对象)的工作。</para>
-
- <para>也许你只在应用(可能是servlet)生命周期的开始<emphasis>执行一次</emphasis>:</para>
-
- <programlisting role="unspecified">// Create your Configuration instance, and specify if up to what FreeMarker
-// version (here 2.3.22) do you want to apply the fixes that are not 100%
-// backward-compatible. See the Configuration JavaDoc for details.
-Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-
-// Specify the source where the template files come from. Here I set a
-// plain directory for it, but non-file-system sources are possible too:
-cfg.setDirectoryForTemplateLoading(new File("<replaceable>/where/you/store/templates</replaceable>"));
-
-// Set the preferred charset template files are stored in. UTF-8 is
-// a good choice in most applications:
-cfg.setDefaultEncoding("UTF-8");
-
-// Sets how errors will appear.
-// During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is better.
-cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);</programlisting>
-
- <para>从现在开始,应该使用 <emphasis>单</emphasis> 实例配置(也就是说,它是单例的)。
- 请注意不管一个系统有多少独立的组件来使用 FreeMarker,
- 它们都会使用他们自己私有的 <literal>Configuration</literal> 实例。</para>
-
- <warning>
- <para>不需要重复创建 <literal>Configuration</literal> 实例;
- 它的代价很高,尤其是会丢失缓存。<literal>Configuration</literal>
- 实例就是应用级别的单例。</para>
- </warning>
-
- <para>当使用多线程应用程序(比如Web网站),<literal>Configuration</literal>
- 实例中的设置就不能被修改。它们可以被视作为 <quote>有效的不可改变的</quote>
- 对象, 也可以继续使用 <emphasis>安全发布</emphasis> 技术
- (参考 JSR 133 和相关的文献)来保证实例对其它线程也可用。比如,
- 通过final或volatile字段来声明实例,或者通过线程安全的IoC容器,但不能作为普通字段。
- (<literal>Configuration</literal> 中不处理修改设置的方法是线程安全的。)</para>
- </section>
-
- <section xml:id="pgui_quickstart_createdatamodel">
- <title>创建数据模型</title>
-
- <indexterm>
- <primary>data-model</primary>
-
- <secondary>assembling with Java</secondary>
- </indexterm>
-
- <para>在简单的示例中你可以使用 <literal>java.lang</literal> 和
- <literal>java.util</literal> 包中的类,
- 还有用户自定义的Java Bean来构建数据对象:</para>
-
- <itemizedlist>
- <listitem>
- <para>使用 <literal>java.lang.String</literal> 来构建字符串。</para>
- </listitem>
-
- <listitem>
- <para>使用 <literal>java.lang.Number</literal> 来派生数字类型。</para>
- </listitem>
-
- <listitem>
- <para>使用 <literal>java.lang.Boolean</literal> 来构建布尔值。</para>
- </listitem>
-
- <listitem>
- <para>使用 <literal>java.util.List</literal> 或Java数组来构建序列。</para>
- </listitem>
-
- <listitem>
- <para>使用 <literal>java.util.Map</literal> 来构建哈希表。</para>
- </listitem>
-
- <listitem>
- <para>使用自定义的bean类来构建哈希表,bean中的项和bean的属性对应。比如,
- <literal>product</literal> 的 <literal>price</literal> 属性
- (<literal>getProperty()</literal>)可以通过 <literal>product.price</literal>
- 获取。(bean的action也可以通过这种方式拿到;
- 要了解更多可以参看 <link linkend="pgui_misc_beanwrapper">这里</link>)</para>
- </listitem>
- </itemizedlist>
-
- <para>我们为 <link
- linkend="example.first">模板开发指南部分演示的第一个例子</link>
- 来构建数据模型。为了方便说明,这里再展示一次示例:</para>
-
- <programlisting role="dataModel">(root)
- |
- +- user = "Big Joe"
- |
- +- latestProduct
- |
- +- url = "products/greenmouse.html"
- |
- +- name = "green mouse"</programlisting>
-
- <para>下面是构建这个数据模型的Java代码片段:</para>
-
- <programlisting role="unspecified">// Create the root hash
-Map<String, Object> root = new HashMap<>();
-// Put string ``user'' into the root
-root.put("user", "Big Joe");
-// Create the hash for ``latestProduct''
-Map<String, Object> latest = new HashMap<>();
-// and put it into the root
-root.put("latestProduct", latest);
-// put ``url'' and ``name'' into latest
-latest.put("url", "products/greenmouse.html");
-latest.put("name", "green mouse");</programlisting>
-
- <para>在真实应用系统中,通常会使用应用程序指定的类来代替 <literal>Map</literal>,
- 它会有JavaBean规范规定的
- <literal>get<replaceable>Xxx</replaceable></literal>/<literal>is<replaceable>Xxx</replaceable></literal>
- 方法。比如有一个和下面类似的类:</para>
-
- <programlisting role="unspecified">public class Product {
-
- private String url;
- private String name;
- ...
-
- // As per the JavaBeans spec., this defines the "url" bean property
- public String getUrl() {
- return url;
- }
-
- // As per the JavaBean spec., this defines the "name" bean property
- public String getName() {
- return name;
- }
-
- ...
-
-}</programlisting>
-
- <para>将它的实例放入数据模型中,就像下面这样:</para>
-
- <programlisting role="unspecified">Product latestProducts = getLatestProductFromDatabaseOrSomething();
-root.put("latestProduct", latestProduct);</programlisting>
-
- <para>如果<literal>latestProduct</literal> 是 <literal>Map</literal>类型,
- 模板就可以是相同的,比如 <literal>${latestProduct.name}</literal>
- 在两种情况下都好用。</para>
-
- <para>根root本身也无需是 <literal>Map</literal>,只要是有
- <literal>getUser()</literal> 和 <literal>getLastestProduct()</literal>
- 方法的对象即可。</para>
-
- <note>
- <para>如果配置设置项 <literal>object_wrapper</literal> 的值是用于所有真实步骤,
- 这里描述的行为才好用。任何由 <literal>ObjectWrapper</literal> 包装成的哈希表
- 可以用作根root,也可以在模板中和点、 <literal>[]</literal> 操作符使用。
- 如果不是包装成哈希表的对象不能作为根root,也不能像那样在模板中使用。</para>
- </note>
- </section>
-
- <section xml:id="pgui_quickstart_gettemplate">
- <title>获取模板</title>
-
- <indexterm>
- <primary>template</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>模板代表了 <literal>freemarker.template.Template</literal>
- 实例。典型的做法是从 <literal>Configuration</literal> 实例中获取一个
- <literal>Template</literal> 实例。无论什么时候你需要一个模板实例,
- 都可以使用它的 <literal>getTemplate</literal> 方法来获取。在 <link
- linkend="pgui_quickstart_createconfiguration">之前</link> 设置的目录中的
- <literal>test.ftl</literal> 文件中存储 <link
- linkend="example.first">示例模板</link>,那么就可以这样来做:</para>
-
- <programlisting role="unspecified">Template temp = cfg.getTemplate("test.ftl");</programlisting>
-
- <para>当调用这个方法的时候,将会创建一个 <literal>test.ftl</literal>
- 的 <literal>Template</literal> 实例,通过读取
- <literal><replaceable>/where/you/store/templates/</replaceable>test.ftl</literal>
- 文件,之后解析(编译)它。<literal>Template</literal> 实例以解析后的形式存储模板,
- 而不是以源文件的文本形式。</para>
-
- <para><literal>Configuration</literal> 缓存 <literal>Template</literal>
- 实例,当再次获得 <literal>test.ftl</literal> 的时候,它可能再读取和解析模板文件了,
- 而只是返回第一次的 <literal>Template</literal> 实例。</para>
- </section>
-
- <section xml:id="pgui_quickstart_merge">
- <title>合并模板和数据模型</title>
-
- <indexterm>
- <primary>output</primary>
-
- <secondary>generate with Java</secondary>
- </indexterm>
-
- <indexterm>
- <primary>merging</primary>
- </indexterm>
-
- <para>我们已经知道,数据模型+模板=输出,我们有了一个数据模型
- (<literal>root</literal>) 和一个模板 (<literal>temp</literal>),
- 为了得到输出就需要合并它们。这是由模板的 <literal>process</literal>
- 方法完成的。它用数据模型root和 <literal>Writer</literal>
- 对象作为参数,然后向 <literal>Writer</literal> 对象写入产生的内容。
- 为简单起见,这里我们只做标准的输出:</para>
-
- <programlisting role="unspecified">Writer out = new OutputStreamWriter(System.out);
-temp.process(root, out);</programlisting>
-
- <para>这会向你的终端输出你在模板开发指南部分的 <link
- linkend="example.first">第一个示例</link> 中看到的内容。</para>
-
- <para>Java I/O 相关注意事项:基于 <literal>out</literal> 对象,必须保证
- <literal>out.close()</literal> 最后被调用。当 <literal>out</literal>
- 对象被打开并将模板的输出写入文件时,这是很电影的做法。其它时候,
- 比如典型的Web应用程序,那就 <emphasis>不能</emphasis> 关闭
- <literal>out</literal> 对象。FreeMarker 会在模板执行成功后
- (也可以在 <literal>Configuration</literal> 中禁用)
- 调用 <literal>out.flush()</literal>,所以不必为此担心。</para>
-
- <para>请注意,一旦获得了 <literal>Template</literal> 实例,
- 就能将它和不同的数据模型进行不限次数
- (<literal>Template</literal>实例是无状态的)的合并。此外,
- 当 <literal>Template</literal> 实例创建之后
- <literal>test.ftl</literal> 文件才能访问,而不是在调用处理方法时。</para>
- </section>
-
- <section xml:id="pgui_quickstart_all">
- <title>将代码放在一起</title>
-
- <para>这是一个由之前的代码片段组合在一起的源程序文件。
- 千万不要忘了将 <literal>freemarker.jar</literal>
- 放到 <literal>CLASSPATH</literal> 中。</para>
-
- <programlisting role="unspecified">import freemarker.template.*;
-import java.util.*;
-import java.io.*;
-
-public class Test {
-
- public static void main(String[] args) throws Exception {
-
- /* ------------------------------------------------------------------------ */
- /* You should do this ONLY ONCE in the whole application life-cycle: */
-
- /* Create and adjust the configuration singleton */
- Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
- cfg.setDirectoryForTemplateLoading(new File("<replaceable>/where/you/store/templates</replaceable>"));
- cfg.setDefaultEncoding("UTF-8");
- cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW);
-
- /* ------------------------------------------------------------------------ */
- /* You usually do these for MULTIPLE TIMES in the application life-cycle: */
-
- /* Create a data-model */
- Map root = new HashMap();
- root.put("user", "Big Joe");
- Map latest = new HashMap();
- root.put("latestProduct", latest);
- latest.put("url", "products/greenmouse.html");
- latest.put("name", "green mouse");
-
- /* Get the template (uses cache internally) */
- Template temp = cfg.getTemplate("test.ftl");
-
- /* Merge data-model with template */
- Writer out = new OutputStreamWriter(System.out);
- temp.process(root, out);
- // Note: Depending on what `out` is, you may need to call `out.close()`.
- // This is usually the case for file output, but not for servlet output.
- }
-}</programlisting>
-
- <note>
- <para>为了简单起见,这里压制了异常(在方法签名中声明了异常,译者注),
- 而在正式运行的产品中不要这样做。</para>
- </note>
- </section>
- </chapter>
-
- <chapter xml:id="pgui_datamodel">
- <title>数据模型</title>
-
- <para>这只是一个介绍性的说明,可以查看 <olink
- targetdoc="api">FreeMarker Java API文档</olink> 获取更多信息。</para>
-
- <section xml:id="pgui_datamodel_basics">
- <title>基本内容</title>
-
- <indexterm>
- <primary>object wrapper</primary>
- </indexterm>
-
- <indexterm>
- <primary>wrapper</primary>
- </indexterm>
-
- <indexterm>
- <primary>data-model</primary>
-
- <secondary>assembling with Java, without object wrapper</secondary>
- </indexterm>
-
- <para>在 <link linkend="pgui_quickstart">入门</link> 章节中,
- 我们已经知道如何使用基本的Java类(<literal>Map</literal>,
- <literal>String</literal>,等)来构建数据模型了。在内部,模板中可用的变量都是实现了
- <literal>freemarker.template.TemplateModel</literal> 接口的Java对象。
- 但在数据模型中,可以使用基本的Java集合类作为变量,因为这些变量会在内部被替换为适当的
- <literal>TemplateModel</literal> 类型。这种功能特性被称作是
- <emphasis role="term">对象包装</emphasis>。对象包装功能可以透明地把
- <emphasis>任何</emphasis> 类型的对象转换为实现了 <literal>TemplateModel</literal>
- 接口类型的实例。这就使得下面的转换成为可能,如在模板中把
- <literal>java.sql.ResultSet</literal> 转换为序列变量,
- 把 <literal>javax.servlet.ServletRequest</literal> 对象转换成包含请求属性的哈希表变量,
- 甚至可以遍历XML文档作为FTL变量(<link linkend="xgui">参考这里</link>)。包装(转换)这些对象,
- 需要使用合适的,也就是所谓的 <literal>对象包装器</literal> 实现(可能是自定义的实现);
- 这将在<link linkend="pgui_datamodel_objectWrapper">后面</link>讨论。
- 现在的要点是想从模板访问任何对象,它们早晚都要转换为实现了 <literal>TemplateModel</literal>
- 接口的对象。那么首先你应该熟悉来写 <literal>TemplateModel</literal> 接口的实现类。</para>
-
- <para>有一个粗略的 <literal>freemarker.template.TemplateModel</literal>
- 子接口对应每种基本变量类型(<literal>TemplateHashModel</literal> 对应哈希表,
- <literal>TemplateSequenceModel</literal> 对应序列,
- <literal>TemplateNumberModel</literal> 对应数字等等)。比如,想为模板使用
- <literal>java.sql.ResultSet</literal> 变量作为一个序列,那么就需要编写一个
- <literal>TemplateSequenceModel</literal> 的实现类,这个类要能够读取
- <literal>java.sql.ResultSet</literal> 中的内容。我们常这么说,使用
- <literal>TemplateModel</literal> 的实现类 <emphasis>包装</emphasis> 了
- <literal>java.sql.ResultSet</literal>,基本上只是封装
- <literal>java.sql.ResultSet</literal> 来提供使用普通的
- <literal>TemplateSequenceModel</literal> 接口访问它。请注意一个类可以实现多个
- <literal>TemplateModel</literal> 接口;这就是为什么FTL变量可以有多种类型
- (参看 <xref linkend="dgui_datamodel_basics"/>)</para>
-
- <para>请注意,这些接口的一个细小的实现是和
- <literal>freemarker.template</literal> 包一起提供的。
- 例如,将一个 <literal>String</literal> 转换成FTL的字符串变量,
- 可以使用 <literal>SimpleScalar</literal>,将 <literal>java.util.Map</literal>
- 转换成FTL的哈希表变量,可以使用 <literal>SimpleHash</literal> 等等。</para>
-
- <para>如果想尝试自己的 <literal>TemplateModel</literal> 实现,
- 一个简单的方式是创建它的实例,然后将这个实例放入数据模型中
- (也就是把它 <literal>放到</literal> 哈希表的根root上)。
- 对象包装器将会给模板提供它的原状,因为它已经实现了
- <literal>TemplateModel</literal> 接口,所以没有转换(包装)的需要。
- (这个技巧当你不想用对象包装器来包装(转换)某些对象时仍然有用。)</para>
- </section>
-
- <section xml:id="pgui_datamodel_scalar">
- <title>标量</title>
-
- <indexterm>
- <primary>scalar</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>number</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>boolean</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>date</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>time</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>有4种类型的标量:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>布尔值</para>
- </listitem>
-
- <listitem>
- <para>数字</para>
- </listitem>
-
- <listitem>
- <para>字符串</para>
- </listitem>
-
- <listitem>
- <para>日期类型(子类型: 日期(没有时间部分),时间或者日期-时间)</para>
- </listitem>
- </itemizedlist>
-
- <para>每一种标量类型都是
- <literal>Template<replaceable>Type</replaceable>Model</literal>
- 接口的实现,这里的 <literal><replaceable>Type</replaceable></literal>
- 就是类型的名称。这些接口只定义了一个方法:
- <literal><replaceable>type</replaceable>
- getAs<replaceable>Type</replaceable>();</literal>。
- 它返回变量的Java类型(<literal>boolean</literal>,
- <literal>Number</literal>, <literal>String</literal> 和
- <literal>Date</literal> 各自代表的值)。</para>
-
- <note>
- <para>由于历史遗留的原因,字符串标量的接口是
- <literal>TemplateScalarModel</literal>,而不是
- <literal>TemplateStringModel</literal>。
- (因为早期的 FreeMarker 字符串就是标量。)</para>
- </note>
-
- <para>这些接口的一个细小的实现和
- <literal>Simple<replaceable>Type</replaceable></literal>
- 类名在 <literal>freemarker.template</literal> 包中是可用的。
- 但是却没有 <literal>SimpleBooleanModel</literal> 类型;为了代表布尔值,
- 可以使用 <literal>TemplateBooleanModel.TRUE</literal> 和
- <literal>TemplateBooleanModel.FALSE</literal> 来单独使用。</para>
-
- <note>
- <para>由于历史遗留的原因,字符串标量的实现类是
- <literal>SimpleScalar</literal>,而不是
- <literal>SimpleString</literal>。</para>
- </note>
-
- <para>在FTL中标量是一成不变的。当在模板中设置变量的值时,
- 使用其他的实例来替换
- <literal>Template<replaceable>Type</replaceable>Model</literal> 实例时,
- 是不用改变原来实例中存储的值的。</para>
-
- <section>
- <title><quote>日期</quote> 类型的难点</title>
-
- <indexterm>
- <primary>date</primary>
-
- <secondary>Java API related difficulties</secondary>
- </indexterm>
-
- <indexterm>
- <primary>time</primary>
-
- <secondary>Java API related difficulties</secondary>
- </indexterm>
-
- <para>对于日期类型来说,有一些难题,因为Java API通常不区别
- <literal>java.util.Date</literal> 只存储日期部分(April 4, 2003),
- 时间部分(10:19:18 PM),或两者都存(April 4, 2003 10:19:18 PM)。
- 为了用本文正确显示值(或者进行其它确定的操作),FreeMarker必须知道
- <literal>java.util.Date</literal> 的哪个部分存储了有意义上的信息,
- 哪部分没有被使用(通常是标记为0的)。不幸的是,
- 通常该信息只是当值从数据库中取得时可用,
- 因为大多数数据库有独立的日期,时间和日期-时间(又叫做时间戳)类型,
- <literal>java.sql</literal> 有3个对应的
- <literal>java.util.Date</literal> 子类和它们相匹配。</para>
-
- <para><literal>TemplateDateModel</literal> 接口有两个方法:分别是
- <literal>java.util.Date getAsDate()</literal> 和 <literal>int getDateType()</literal>。
- 该接口典型的实现是存储一个 <literal>java.util.Date</literal> 对象,
- 加上一个整数来辨别子类型。这个整数的值也必须是 <literal>TemplateDateModel</literal>
- 接口中的常量之一:<literal>DATE</literal>, <literal>TIME</literal>,
- <literal>DATETIME</literal> 和 <literal>UNKNOWN</literal>。</para>
-
- <para>关于 <literal>UNKNOWN</literal>: <literal>java.lang</literal>
- 和 <literal>java.util</literal> 下的类通常被自动转换成
- <literal>TemplateModel</literal> 的实现类,就是所谓的
- <literal>对象包装器ObjectWrapper</literal>(请参考之前的对象包装介绍)。
- 如果对象包装器要包装 <literal>java.util.Date</literal> 类,
- 它不是 <literal>java.sql</literal> 日期类的实例,那就不能决定子类型是什么,
- 所以使用 <literal>UNKNOWN</literal>。之后,如果模板需要使用这个变量,
- 而且操作也需要子类型,那就会停止执行并抛出错误。为了避免这种情况的发生,
- 对于那些可能有问题的变量,模板开发人员必须明确地指定子类型,使用内建函数 <link
- linkend="ref_builtin_date_datetype"><literal>date</literal>,
- <literal>time</literal> 或 <literal>datetime</literal></link>
- (比如 <literal>lastUpdated?datetime</literal>)。请注意,
- 如果和格式化参数一起使用内建函数 <literal>string</literal>,
- 比如foo?string("MM/dd/yyyy"),那么 FreeMarker 就不必知道子类型了。</para>
- </section>
- </section>
-
- <section xml:id="pgui_datamodel_parent">
- <title>容器</title>
-
- <indexterm>
- <primary>containers</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>容器包括哈希表,序列和集合三种类型。</para>
-
- <section>
- <title>哈希表</title>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>哈希表是实现了 <literal>TemplateHashModel</literal>
- 接口的Java对象。<literal>TemplateHashModel</literal> 有两个方法:
- <literal>TemplateModel get(String key)</literal>,这个方法根据给定的名称返回子变量,
- <literal>boolean isEmpty()</literal>,这个方法表明哈希表是否含有子变量。
- <literal>get</literal> 方法当在给定的名称没有找到子变量时返回null。</para>
-
- <para><literal>TemplateHashModelEx</literal> 接口扩展了
- <literal>TemplateHashModel</literal>。它增加了更多的方法,使得可以使用内建函数
- <link linkend="ref_builtin_values">values</link> 和
- <link linkend="ref_builtin_keys">keys</link> 来枚举哈希表中的子变量。</para>
-
- <para>经常使用的实现类是 <literal>SimpleHash</literal>,该类实现了
- <literal>TemplateHashModelEx</literal> 接口。从内部来说,它使用一个
- <literal>java.util.Hash</literal> 类型的对象存储子变量。
- <literal>SimpleHash</literal> 类的方法可以添加和移除子变量。
- 这些方法应该用来在变量被创建之后直接初始化。</para>
-
- <para>在FTL中,容器是一成不变的。那就是说你不能添加,替换和移除容器中的子变量。</para>
- </section>
-
- <section>
- <title>序列</title>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>序列是实现了 <literal>TemplateSequenceModel</literal>
- 接口的Java对象。它包含两个方法:<literal>TemplateModel get(int index)</literal>
- 和 <literal>int size()</literal>。</para>
-
- <para>经常使用的实现类是 <literal>SimpleSequence</literal>。该类内部使用一个
- <literal>java.util.List</literal> 类型的对象存储它的子变量。
- <literal>SimpleSequence</literal> 有添加子元素的方法。
- 在序列创建之后应该使用这些方法来填充序列。</para>
- </section>
-
- <section>
- <title>集合</title>
-
- <indexterm>
- <primary>collection</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>集合是实现了 <literal>TemplateCollectionModel</literal>
- 接口的Java对象。这个接口定义了一个方法:
- <literal>TemplateModelIterator iterator()</literal>。
- <literal>TemplateModelIterator</literal> 接口和
- <literal>java.util.Iterator</literal> 相似,但是它返回
- <literal>TemplateModels</literal> 而不是 <literal>Object</literal>,
- 而且它能抛出 <literal>TemplateModelException</literal> 异常。</para>
-
- <para>通常使用的实现类是 <literal>SimpleCollection</literal>。</para>
- </section>
- </section>
-
- <section xml:id="pgui_datamodel_method">
- <title>方法</title>
-
- <indexterm>
- <primary>method</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>方法变量在存于实现了 <literal>TemplateMethodModel</literal>
- 接口的模板中。这个接口包含一个方法:
- <literal>TemplateModel exec(java.util.List arguments)</literal>。
- 当使用 <link linkend="dgui_template_exp_methodcall">方法调用表达式</link>
- 调用方法时,<literal>exec</literal> 方法将会被调用。
- 形参将会包含FTL方法调用形参的值。<literal>exec</literal>
- 方法的返回值给出了FTL方法调用表达式的返回值。</para>
-
- <para><literal>TemplateMethodModelEx</literal> 接口扩展了
- <literal>TemplateMethodModel</literal> 接口。它没有添加任何新方法。
- 事实上这个对象实现这个 <emphasis>标记</emphasis> 接口是给FTL引擎暗示,
- 形式参数应该直接以 <literal>TemplateModel</literal> 的形式放进
- <literal>java.util.List</literal>。否则将会以
- <literal>String</literal> 形式放入list。</para>
-
- <para>出于这些很明显的原因,这些接口没有默认的实现。</para>
-
- <para>例如:下面这个方法,返回第一个字符串在第二个字符串第一次出现时的索引位置,
- 如果第二个字符串中不包含第一个字符串,则返回-1:</para>
-
- <programlisting role="unspecified">public class IndexOfMethod implements TemplateMethodModel {
-
- public TemplateModel exec(List args) throws TemplateModelException {
- if (args.size() != 2) {
- throw new TemplateModelException("Wrong arguments");
- }
- return new SimpleNumber(
- ((String) args.get(1)).indexOf((String) args.get(0)));
- }
-}</programlisting>
-
- <para>如果将一个实例放入根数据模型中,像这样:</para>
-
- <programlisting role="unspecified">root.put("indexOf", new IndexOfMethod());</programlisting>
-
- <para>那么就可以在模板中调用:</para>
-
- <programlisting role="template"><#assign x = "something">
-${indexOf("met", x)}
-${indexOf("foo", x)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">2
--1</programlisting>
-
- <para>如果需要访问FTL运行时环境(读/写变量,获取本地化信息等),则可以使用
- <literal>Environment.getCurrentEnvironment()</literal> 来获取。</para>
- </section>
-
- <section xml:id="pgui_datamodel_directive">
- <title>指令</title>
-
- <indexterm>
- <primary>directives</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <para>Java程序员可以使用 <literal>TemplateDirectiveModel</literal>
- 接口在Java代码中实现自定义指令。详情可以参加API文档。</para>
-
- <note>
- <para><literal>TemplateDirectiveModel</literal> 在 FreeMarker 2.3.11 版本时才加入,
- 来代替快被废弃的 <literal>TemplateTransformModel</literal>。</para>
- </note>
-
- <section>
- <title>示例 1</title>
-
- <para>我们要实现一个指令,
- 这个指令可以将在它开始标签和结束标签之内的字符都转换为大写形式。
- 就像这个模板:</para>
-
- <programlisting role="template">foo
-<emphasis><@upper></emphasis>
- bar
- <#-- All kind of FTL is allowed here -->
- <#list ["red", "green", "blue"] as color>
- ${color}
- </#list>
- baaz
-<emphasis></@upper></emphasis>
-wombat</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">foo
- BAR
- RED
- GREEN
- BLUE
- BAAZ
-wombat</programlisting>
-
- <para>下面是指令类的源代码:</para>
-
- <programlisting role="unspecified">package com.example;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Map;
-
-import freemarker.core.Environment;
-import freemarker.template.TemplateDirectiveBody;
-import freemarker.template.TemplateDirectiveModel;
-import freemarker.template.TemplateException;
-import freemarker.template.TemplateModel;
-import freemarker.template.TemplateModelException;
-
-/**
- * FreeMarker user-defined directive that progressively transforms
- * the output of its nested content to upper-case.
- *
- *
- * <p><b>Directive info</b></p>
- *
- * <p>Directive parameters: None
- * <p>Loop variables: None
- * <p>Directive nested content: Yes
- */
-public class UpperDirective implements TemplateDirectiveModel {
-
- public void execute(Environment env,
- Map params, TemplateModel[] loopVars,
- TemplateDirectiveBody body)
- throws TemplateException, IOException {
- // Check if no parameters were given:
- if (!params.isEmpty()) {
- throw new TemplateModelException(
- "This directive doesn't allow parameters.");
- }
- if (loopVars.length != 0) {
- throw new TemplateModelException(
- "This directive doesn't allow loop variables.");
- }
-
- // If there is non-empty nested content:
- if (body != null) {
- // Executes the nested body. Same as <#nested> in FTL, except
- // that we use our own writer instead of the current output writer.
- body.render(new UpperCaseFilterWriter(env.getOut()));
- } else {
- throw new RuntimeException("missing body");
- }
- }
-
- /**
- * A {@link Writer} that transforms the character stream to upper case
- * and forwards it to another {@link Writer}.
- */
- private static class UpperCaseFilterWriter extends Writer {
-
- private final Writer out;
-
- UpperCaseFilterWriter (Writer out) {
- this.out = out;
- }
-
- public void write(char[] cbuf, int off, int len)
- throws IOException {
- char[] transformedCbuf = new char[len];
- for (int i = 0; i < len; i++) {
- transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]);
- }
- out.write(transformedCbuf);
- }
-
- public void flush() throws IOException {
- out.flush();
- }
-
- public void close() throws IOException {
- out.close();
- }
- }
-
-}</programlisting>
-
- <para>现在我们需要创建这个类的实例,
- 然后让这个指令在模板中可以通过名称"upper"来访问
- (或者是其它我们想用的名字)。一个可行的方案是把这个指令放到数据模型中:</para>
-
- <programlisting role="unspecified">root.put("upper", new com.example.UpperDirective());</programlisting>
-
- <para>但更好的做法是将常用的指令作为 <link
- linkend="pgui_config_sharedvariables">共享变量</link>
- 放到 <literal>Configuration</literal> 中。</para>
-
- <para>当然也可以使用 <link
- linkend="ref_builtin_new">内建函数<literal>new</literal></link>
- 将指令放到一个FTL库(宏的集合,就像在模板中,
- 使用 <literal>include</literal> 或 <literal>import</literal> )中:</para>
-
- <programlisting role="template"><#-- Maybe you have directives that you have implemented in FTL -->
-<#macro something>
- ...
-</#macro>
-
-<#-- Now you can't use <#macro upper>, but instead you can: -->
-<#assign upper = "com.example.UpperDirective"?new()>
-</programlisting>
- </section>
-
- <section>
- <title>示例 2</title>
-
- <para>我们来创建一个指令,这个指令可以一次又一次地执行其中的嵌套内容,
- 这个次数由指定的数字来确定(就像 <literal>list</literal> 指令),
- 可以使用<literal><hr></literal>将输出的重复内容分开。
- 这个指令我们命名为"repeat"。示例模板如下:</para>
-
- <programlisting role="template"><#assign x = 1>
-
-<emphasis><@repeat count=4></emphasis>
- Test ${x}
- <#assign x++>
-<emphasis></@repeat></emphasis>
-
-<emphasis><@repeat count=3 hr=true></emphasis>
- Test
-<emphasis></@repeat></emphasis>
-
-<emphasis><@repeat count=3; cnt></emphasis>
- ${cnt}. Test
-<emphasis></@repeat></emphasis></programlisting>
-
- <para>输出为:</para>
-
- <programlisting role="output"> Test 1
- Test 2
- Test 3
- Test 4
-
- Test
-<hr> Test
-<hr> Test
-
- 1. Test
- 2. Test
- 3. Test
- </programlisting>
-
- <para>指令的实现类为:</para>
-
- <programlisting role="unspecified">package com.example;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Iterator;
-import java.util.Map;
-
-import freemarker.core.Environment;
-import freemarker.template.SimpleNumber;
-import freemarker.template.TemplateBooleanModel;
-import freemarker.template.TemplateDirectiveBody;
-import freemarker.template.TemplateDirectiveModel;
-import freemarker.template.TemplateException;
-import freemarker.template.TemplateModel;
-import freemarker.template.TemplateModelException;
-import freemarker.template.TemplateNumberModel;
-
-/**
- * FreeMarker user-defined directive for repeating a section of a template,
- * optionally with separating the output of the repetations with
- * <tt>&lt;hr></tt>-s.
- *
- *
- * <p><b>Directive info</b></p>
- *
- * <p>Parameters:
- * <ul>
- * <li><code>count</code>: The number of repetations. Required!
- * Must be a non-negative number. If it is not a whole number then it will
- * be rounded <em>down</em>.
- * <li><code>hr</code>: Tells if a HTML "hr" element could be printed between
- * repetations. Boolean. Optional, defaults to <code>false</code>.
- * </ul>
- *
- * <p>Loop variables: One, optional. It gives the number of the current
- * repetation, starting from 1.
- *
- * <p>Nested content: Yes
- */
-public class RepeatDirective implements TemplateDirectiveModel {
-
- private static final String PARAM_NAME_COUNT = "count";
- private static final String PARAM_NAME_HR = "hr";
-
- public void execute(Environment env,
- Map params, TemplateModel[] loopVars,
- TemplateDirectiveBody body)
- throws TemplateException, IOException {
-
- // ---------------------------------------------------------------------
- // Processing the parameters:
-
- int countParam = 0;
- boolean countParamSet = false;
- boolean hrParam = false;
-
- Iterator paramIter = params.entrySet().iterator();
- while (paramIter.hasNext()) {
- Map.Entry ent = (Map.Entry) paramIter.next();
-
- String paramName = (String) ent.getKey();
- TemplateModel paramValue = (TemplateModel) ent.getValue();
-
- if (paramName.equals(PARAM_NAME_COUNT)) {
- if (!(paramValue instanceof TemplateNumberModel)) {
- throw new TemplateModelException(
- "The \"" + PARAM_NAME_HR + "\" parameter "
- + "must be a number.");
- }
- countParam = ((TemplateNumberModel) paramValue)
- .getAsNumber().intValue();
- countParamSet = true;
- if (countParam < 0) {
- throw new TemplateModelException(
- "The \"" + PARAM_NAME_HR + "\" parameter "
- + "can't be negative.");
- }
- } else if (paramName.equals(PARAM_NAME_HR)) {
- if (!(paramValue instanceof TemplateBooleanModel)) {
- throw new TemplateModelException(
- "The \"" + PARAM_NAME_HR + "\" parameter "
- + "must be a boolean.");
- }
- hrParam = ((TemplateBooleanModel) paramValue)
- .getAsBoolean();
- } else {
- throw new TemplateModelException(
- "Unsupported parameter: " + paramName);
- }
- }
- if (!countParamSet) {
- throw new TemplateModelException(
- "The required \"" + PARAM_NAME_COUNT + "\" paramter"
- + "is missing.");
- }
-
- if (loopVars.length > 1) {
- throw new TemplateModelException(
- "At most one loop variable is allowed.");
- }
-
- // Yeah, it was long and boring...
-
- // ---------------------------------------------------------------------
- // Do the actual directive execution:
-
- Writer out = env.getOut();
- if (body != null) {
- for (int i = 0; i < countParam; i++) {
- // Prints a <hr> between all repetations if the "hr" parameter
- // was true:
- if (hrParam && i != 0) {
- out.write("<hr>");
- }
-
- // Set the loop variable, if there is one:
- if (loopVars.length > 0) {
- loopVars[0] = new SimpleNumber(i + 1);
- }
-
- // Executes the nested body (same as <#nested> in FTL). In this
- // case we don't provide a special writer as the parameter:
- body.render(env.getOut());
- }
- }
- }
-
-}</programlisting>
- </section>
-
- <section>
- <title>注意</title>
-
- <para><literal>TemplateDirectiveModel</literal>
- 对象通常不应该是有状态的,这一点非常重要。
- 一个经常犯的错误是存储指令的状态然后在对象的属性中调用执行。
- 想一下相同指令的嵌入调用,或者指令对象被用作共享变量,
- 并通过多线程同时访问。</para>
-
- <para>不幸的是, <literal>TemplateDirectiveModel</literal>
- 不支持传递参数的位置(而不是参数名称)。从 FreeMarker 2.4 版本开始,它将被修正。</para>
- </section>
- </section>
-
- <section xml:id="pgui_datamodel_node">
- <title>结点变量</title>
-
- <indexterm>
- <primary>node</primary>
-
- <secondary>Java side</secondary>
- </indexterm>
-
- <indexterm>
- <primary>tree nodes</primary>
- </indexterm>
-
- <indexterm>
- <primary>trees</primary>
- </indexterm>
-
- <para>结点变量体现了树形结构中的结点。结点变量的引入是为了帮助用户 <link
- linkend="xgui">在数据模型中处理XML文档</link>,
- 但是它们也可以用于构建树状模型。如需要有关从模板语言角度考虑的结点信息,
- 那么可以 <link linkend="dgui_datamodel_node">阅读之前章节</link>。</para>
-
- <para>结点变量有下列属性,它们都由
- <literal>TemplateNodeModel</literal> 接口的方法提供:</para>
-
- <itemizedlist>
- <listitem>
- <para>基本属性:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>TemplateSequenceModel getChildNodes()</literal>:
- 一个结点的子结点序列(除非这个结点是叶子结点,这时方法返回一个空序列或者是null)。
- 子结点本身应该也是结点变量。</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateNodeModel getParentNode()</literal>:
- 一个结点只有一个父结点(除非这个结点是结点树的根结点,
- 这时方法返回<literal>null</literal>)。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>可选属性。如果一个属性在具体的使用中没有意义,
- 那对应的方法应该返回<literal>null</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>String getNodeName()</literal>:
- 结点名称也是宏的名称,当使用 <link
- linkend="ref.directive.recurse"><literal>recurse</literal></link> 和 <link
- linkend="ref.directive.visit"><literal>visit</literal></link>指令时,
- 它用来控制结点。因此,如果想通过结点使用这些指令,
- 那么结点的名称是 <emphasis>必须的</emphasis>。</para>
- </listitem>
-
- <listitem>
- <para><literal>String getNodeType()</literal>:在XML中:
- <literal>"element"</literal>,<literal>"text"</literal>,
- <literal>"comment"</literal>等。如果这些信息可用,
- 就是通过 <literal>recurse</literal> 和 <literal>visit</literal>
- 指令来查找结点的默认处理宏。而且,它对其他有具体用途的应用程序也是有用的。</para>
- </listitem>
-
- <listitem>
- <para><literal>String getNamespaceURI()</literal>:
- 这个结点所属的命名空间(和用于库的FTL命名空间无关)。例如,在XML中,
- 这就是元素和属性所属XML命名空间的URI。这个信息如果可用,就是通过
- <literal>recurse</literal> 和 <literal>visit</literal>
- 指令来查找存储控制宏的FTL命名空间。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <para>在FTL里,结点属性的直接使用可以通过 <link
- linkend="ref_builtins_node">内建函数 node</link>完成,
- 还有 <literal>visit</literal> 和 <literal>recurse</literal> 宏。</para>
-
- <para>在很多用例中,实现了 <literal>TemplateNodeModel</literal>
- 接口和其它接口的变量,因为结点变量属性仅仅提供基本的结点间导航的方法。
- 需要具体的例子,请参考 <link linkend="xgui">FreeMarker如何处理XML</link> 部分。</para>
- </section>
-
- <section xml:id="pgui_datamodel_objectWrapper">
- <title>对象包装</title>
-
- <indexterm>
- <primary>object wrapper</primary>
- </indexterm>
-
- <indexterm>
- <primary>wrapper</primary>
- </indexterm>
-
- <para>对象包装器是实现了 <literal>freemarker.template.ObjectWrapper</literal>
- 接口的类。它的目标是实现Java对象(应用程序中特定类等,比如 <literal>String</literal>,
- <literal>Map</literal>,<literal>List</literal> 实例)和FTL类型系统之间的映射。换句话说,
- 它指定了模板如何在数据模型(包含从模板中调用的Java方法的返回值)中发现Java对象。
- 对象包装器作为插件放入 <literal>Configuration</literal> 中,可以使用
- <literal>object_wrapper</literal> 属性设置
- (或者使用<literal>Configuration.setObjectWrapper</literal>)。</para>
-
- <para>从技术角度来说,FTL类型系统由之前介绍过的 <literal>TemplateModel</literal> 子接口
- (<literal>TemplateScalarModel</literal>,<literal>TemplateHashMode</literal>,
- <literal>TemplateSequenceModel</literal>等)来表示。要映射Java对象到FTL类型系统中,
- 对象包装器的 <literal>TemplateModel wrap(java.lang.Object obj)</literal> 方法会被调用。</para>
-
- <para>有时FreeMarker需要撤回映射,此时 <literal>对象包装器ObjectWrapper</literal>
- 的 <literal>Object unwrap(TemplateModel)</literal> 方法就被调用了
- (或其他的变化,请参考API文档来获取详细内容)。最后的操作是在
- <literal>ObjectWrapperAndUnwrapper</literal> 中,它是
- <literal>ObjectWrapper</literal> 的子接口。很多实际的包装器会实现
- <literal>ObjectWrapperAndUnwrapper</literal> 接口。</para>
-
- <para>我们来看一下包装Java对象并包含其他对象
- (比如 <literal>Map</literal>,<literal>List</literal>,数组,
- 或者有JavaBean属性的对象)是如何进行的。可以这么说,对象包装器将
- <literal>Object[]</literal> 数组包装成 <literal>TemplateSquenceModel</literal>
- 接口的一些实现。当FreeMarker需要FTL序列中项的时候,它会调用
- <literal>TemplateSquenceModel.get(int index)</literal> 方法。该方法的返回值是
- <literal>TemplateModel</literal>,也就是说,<literal>TemplateSquenceModel</literal>
- 实现不仅仅可以从给定的数组序列获取 <literal>对象</literal>,
- 也可以负责在返回它之前包装该值。为了解决这个问题,典型的
- <literal>TemplateSquenceModel</literal> 实现将会存储它创建的
- <literal>ObjectWrapper</literal>,之后再调用该 <literal>ObjectWrapper</literal>
- 来包装包含的值。相同的逻辑代表了 <literal>TemplateHashModel</literal> 或其他的
- <literal>TemplateModel</literal>,它是其它 <literal>TemplateModel</literal> 的容器。
- 因此,通常不论值的层次结构有多深,所有值都会被同一个 <literal>ObjectWrapper</literal>
- 包装。(要创建 <literal>TemplateModel</literal> 的实现类,请遵循这个原则,可以使用
- <literal>freemarker.template.WrappingTemplateModel</literal> 作为基类。)</para>
-
- <para>数据模型本身(root变量)是 <literal>TemplateHashModel</literal>。
- 在 <literal>Template.process</literal> 中指定的root对象将会被在
- <literal>object_wrapper</literal> 配置中设置的对象包装器所包装,并产生一个
- <literal>TemplateHashModel</literal>。从此,被包含值的包装遵循之前描述的逻辑
- (比如,容器负责包装它的子实例)。</para>
-
- <para>行为良好的对象包装器都会绕过已经实现 <literal>TemplateModel</literal>
- 接口的对象。如果将已经实现 <literal>TemplateModel</literal> 的对象放到数据模型中
- (或者从模板中调用的Java方法返回这个对象),那么就可以避免实际的对象包装。
- 当特别是通过模板访问创建的值时,通常会这么做。因此,要避免更多上面对象包装的性能问题,
- 但也可以精确控制模板可以看到的内容(不是基于当前对象包装器的映射策略)。
- 常见的应用程序使用该手法是使用 <literal>freemarker.template.SimpleHash</literal>
- 作为数据模型的根root(而不是<literal>Map</literal>),当使用 <literal>SimpleHash</literal>
- 的 <literal>put</literal> 方法来填充(这点很重要,它不会复制已经填充并存在的
- <literal>Map</literal>)。这会加快顶层数据模型变量的访问速度。</para>
-
- <section xml:id="pgui_datamodel_defaultObjectWrapper">
- <title>默认对象包装器</title>
-
- <indexterm>
- <primary>object wrapper</primary>
-
- <secondary>default</secondary>
- </indexterm>
-
- <indexterm>
- <primary>DefaultObjectWrapper</primary>
- </indexterm>
-
- <para><literal>object_wrapper</literal> <literal>Configuration</literal>
- 的默认设置是 <literal>freemarker.template.DefaultObjectWrapper</literal>
- 实例。除非有特别的需求,那么建议使用这个对象包装器,或者是自定义的
- <literal>DefaultObjectWrapper</literal> 的子类。</para>
-
- <para>它会识别大部分基本的Java类型,比如 <literal>String</literal>,
- <literal>Number</literal>,<literal>Boolean</literal>,
- <literal>Date</literal>,<literal>List</literal> (通常还有全部的
- <literal>java.util.Collection</literal> 类型),
- 数组,<literal>Map</literal>等。并把它们自然地包装成匹配
- <literal>TemplateModel</literal> 接口的对象。它也会使用
- <literal>freemarker.ext.dom.NodeModel</literal> 来包装W3C DOM结点,
- 所以可以很方便地处理XML, <link linkend="xgui">在XML章节会有描述</link>)。
- 对于Jython对象,会代理到 <literal>freemarker.ext.jython.JythonWrapper</literal> 上。
- 而对于其它所有对象,则会调用 <literal>BeansWrapper.wrap</literal>(超类的方法),
- 暴露出对象的JavaBean属性作为哈希表项
- (比如FTL中的 <literal>myObj.foo</literal> 会在后面调用 <literal>getFoo()</literal>),
- 也会暴露出对象(比如FTL中的 <literal>myObj.bar(1, 2)</literal> 就会调用方法)
- 的公有方法(JavaBean action)。(关于对象包装器的更多信息,请参阅
- <link linkend="pgui_misc_beanwrapper">该章节</link>。)</para>
-
- <para>关于 <literal>DefaultObjectWrapper</literal> 更多值得注意的细节:</para>
-
- <itemizedlist>
- <listitem>
- <para>不用经常使用它的构造方法,而是使用
- <literal>DefaultObjectWrapperBuilder</literal> 来创建它。
- 这就允许 FreeMarker 使用单例。</para>
- </listitem>
-
- <listitem xml:id="topic.defaultObjectWrapperIcI">
- <para><literal>DefaultObjectWrapper</literal> 有
- <literal>incompatibleImprovements</literal> 属性,
- 这在 2.3.22 或更高版本中是极力推荐的(参看该效果的 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/DefaultObjectWrapper.html#DefaultObjectWrapper-freemarker.template.Version-">API文档</link>)。如何来设置:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果已经在 2.3.22 或更高版本的
- <emphasis><literal>Configuration</literal></emphasis>
- 中设置了 <literal>incompatible_improvements</literal> 选项,
- 而没有设置 <literal>object_wrapper</literal> 选项(那么它就保留默认值),
- 我们就什么都做不了了,因为它已经使用了同等
- <literal>incompatibleImprovements</literal> 属性值的
- <literal>DefaultObjectWrapper</literal> 单例。</para>
- </listitem>
-
- <listitem xml:id="topic.setDefaultObjectWrapperIcIIndividually">
- <para>另外也可以在 <literal>Configuration</literal> 中独立设置
- <literal>incompatibleImprovements</literal>。基于如何创建/设置
- <literal>ObjectWrapper</literal>,可以通过这样完成
- (假设想要 <literal>incompatibleImprovements</literal> 2.3.22):</para>
-
- <itemizedlist>
- <listitem>
- <para>如果使用了构建器API:</para>
-
- <programlisting role="unspecified">... = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_22).build()</programlisting>
- </listitem>
-
- <listitem>
- <para>或者使用构造方法:</para>
-
- <programlisting role="unspecified">... = new DefaultObjectWrapper(Configuration.VERSION_2_3_22)</programlisting>
- </listitem>
-
- <listitem>
- <para>或者使用 <literal>object_wrapper</literal> 属性
- (<literal>*.properties</literal> 文件或
- <literal>java.util.Properties</literal> 对象):</para>
-
- <programlisting role="unspecified">object_wrapper=DefaultObjectWrapper(2.3.21)</programlisting>
- </listitem>
-
- <listitem>
- <para>或者通过 <literal>FreemarkerServlet</literal> 配置
- <literal>object_wrapper</literal> 和在
- <literal>web.xml</literal> 中的
- <literal>init-param</literal> 属性来配置:</para>
-
- <programlisting role="unspecified"><init-param>
- <param-name>object_wrapper</param-name>
- <param-value>DefaultObjectWrapper(2.3.21)</param-value>
-</init-param></programlisting>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>在新的或测试覆盖良好的项目中,也建议设置
- <literal>forceLegacyNonListCollections</literal>
- 属性为 <literal>false</literal>。
- 如果使用 <literal>.properties</literal> 或
- <literal>FreemarkerServlet</literal> 初始化参数,就会像
- <literal>DefaultObjectWrapper(2.3.22, forceLegacyNonListCollections=false)</literal>,
- 同时,使用Java API可以在 <literal>DefaultObjectWrapperBuilder</literal> 对象调用
- <literal>build()</literal> 之前调用
- <literal>setForceLegacyNonListCollections(false)</literal>。</para>
- </listitem>
-
- <listitem>
- <para>自定义
- <literal>DefaultObjectWrapper</literal> 的最常用方法是覆盖
- <literal>handleUnknownType</literal> 方法。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="pgui_datamodel_customObjectWrappingExample">
- <title>自定义对象包装示例</title>
-
- <indexterm>
- <primary>object wrapper</primary>
-
- <secondary>custom</secondary>
- </indexterm>
-
- <indexterm>
- <primary>custom object wrapper</primary>
- </indexterm>
-
- <indexterm>
- <primary>DefaultObjectWrapper</primary>
-
- <secondary>extending</secondary>
- </indexterm>
-
- <para>我们假定有一个应用程序特定的类,像下面这样:</para>
-
- <programlisting role="unspecified">package com.example.myapp;
-
-public class Tupple<E1, E2> {
- public Tupple(E1 e1, E2 e2) { ... }
- public E1 getE1() { ... }
- public E2 getE2() { ... }
-}</programlisting>
-
- <para>若想让模板将它视作长度为2的序列,那么就可以这么来调用
- <literal>someTupple[1]</literal>,
- <literal><#list someTupple
- <replaceable>...</replaceable>></literal>, 或者
- <literal>someTupple?size</literal>。需要创建一个
- <literal>TemplateSequenceModel</literal> 实现来适配
- <literal>Tupple</literal> 到
- <literal>TempateSequenceMoldel</literal> 接口:</para>
-
- <programlisting role="unspecified">package com.example.myapp.freemarker;
-
-...
-
-public class TuppleAdapter extends WrappingTemplateModel implements TemplateSequenceModel,
- AdapterTemplateModel {
-
- private final Tupple<?, ?> tupple;
-
- public TuppleAdapter(Tupple<?, ?> tupple, ObjectWrapper ow) {
- super(ow); // coming from WrappingTemplateModel
- this.tupple = tupple;
- }
-
- @Override // coming from TemplateSequenceModel
- public int size() throws TemplateModelException {
- return 2;
- }
-
- @Override // coming from TemplateSequenceModel
- public TemplateModel get(int index) throws TemplateModelException {
- switch (index) {
- case 0: return wrap(tupple.getE1());
- case 1: return wrap(tupple.getE2());
- default: return null;
- }
- }
-
- @Override // coming from AdapterTemplateModel
- public Object getAdaptedObject(Class hint) {
- return tupple;
- }
-
-}</programlisting>
-
- <para>关于类和接口:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>TemplateSequenceModel</literal>:
- 这就是为什么模板将它视为序列</para>
- </listitem>
-
- <listitem>
- <para><literal>WrappingTemplateModel</literal>:
- 只是一个方便使用的类,用于 <literal>TemplateModel</literal>
- 对象进行自身包装。通常仅对包含其它对象的对象需要。
- 参考上面的 <literal>wrap(<replaceable>...</replaceable>)</literal> 调用。</para>
- </listitem>
-
- <listitem>
- <para><literal>AdapterTemplateModel</literal>:
- 表明模板模型适配一个已经存在的对象到
- <literal>TemplateModel</literal> 接口,
- 那么去掉包装就会给出原有对象。</para>
- </listitem>
- </itemizedlist>
-
- <para>最后,我们告诉 FreeMarker 用 <literal>TuppleAdapter</literal>
- (或者,可以在将它们传递到FreeMarker之前手动包装它们)
- 包装 <literal>Tupple</literal>。那样的话,首先创建一个自定义的对象包装器:</para>
-
- <programlisting role="unspecified">package com.example.myapp.freemarker;
-
-...
-
-public class MyAppObjectWrapper extends DefaultObjectWrapper {
-
- public MyAppObjectWrapper(Version incompatibleImprovements) {
- super(incompatibleImprovements);
- }
-
- @Override
- protected TemplateModel handleUnknownType(final Object obj) throws TemplateModelException {
- if (obj instanceof Tupple) {
- return new TuppleAdapter((Tupple<?, ?>) obj, this);
- }
-
- return super.handleUnknownType(obj);
- }
-
-}</programlisting>
-
- <para>那么当配置 FreeMarker (<link
- linkend="pgui_config">关于配置,参考这里...</link>)
- 将我们的对象包装器插在:</para>
-
- <programlisting role="unspecified">// Where you initialize the cfg *singleton* (happens just once in the application life-cycle):
-cfg = new Configuration(Configuration.VERSION_2_3_22);
-...
-cfg.setObjectWrapper(new MyAppObjectWrapper(cfg.getIncompatibleImprovements()));</programlisting>
-
- <para>或者使用 <literal>java.util.Properties</literal> 来代替配置
- FreeMarker (也就是 <literal>.properties</literal> 文件):</para>
-
- <programlisting role="unspecified">object_wrapper=com.example.myapp.freemarker.MyAppObjectWrapper(2.3.22)</programlisting>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="pgui_config">
- <title>配置(Configuration)</title>
-
- <indexterm>
- <primary>Configuration</primary>
- </indexterm>
-
- <para>这里仅仅是一个概览,若要了解更多请阅读
- <olink targetdoc="api">FreeMarker Java API 文档</olink>。</para>
-
- <section xml:id="pgui_config_basics">
- <title>基本内容</title>
-
- <para>首先,确保你已经阅读了 <link
- linkend="pgui_quickstart_createconfiguration">入门</link> 章节。</para>
-
- <para>配置(configuration)就是
- <literal>freemarker.template.Configuration</literal> 对象,
- 它存储了常用(全局,应用程序级)的设置,定义了想要在所有模板中可用的变量(称为共享变量)。
- 而且,它会处理 <literal>Template</literal> 实例的新建和缓存。</para>
-
- <para>应用程序典型的用法是使用一个独立的共享
- <literal>Configuration</literal> 实例。更精确来说,
- 典型的做法是每一个独立开发的组件(比如项目,模块等)都有一个
- <literal>Configuration</literal> 实例,它在内部使用FreeMarker,
- 每一个都创建它自己的实例。</para>
-
- <para>运行中的模板会受配置设置的影响,每个 <literal>Template</literal>
- 实例通过对应 <literal>Template</literal> 构造方法参数,都有和它相关联的
- <literal>Configuration</literal> 实例。通常可以使用
- <literal>Configuration.getTemplate</literal>
- (而不是直接调用 <literal>Template</literal> 的构造方法)来获得
- <literal>Template</literal> 实例,此时,关联的 <literal>Configuration</literal>
- 实例就是调用 <literal>getTemplate</literal> 方法的。</para>
- </section>
-
- <section xml:id="pgui_config_sharedvariables">
- <title>共享变量</title>
-
- <indexterm>
- <primary>shared variable</primary>
- </indexterm>
-
- <para><emphasis role="term">Shared variables</emphasis>
- (共享变量)是为所有模板定义的变量。可以使用
- <literal>setSharedVariable</literal> 方法向配置中添加共享变量:</para>
-
- <programlisting role="unspecified">Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-<replaceable>..</replaceable>.
-cfg.setSharedVariable("warp", new WarpDirective());
-cfg.setSharedVariable("company", "Foo Inc.");</programlisting>
-
- <para>在所有使用这个配置的模板中,名为 <literal>wrap</literal>
- 的用户自定义指令和一个名为 <literal>company</literal>
- 的字符串将会在数据模型的根root上可见,
- 那就不用在根哈希表上一次又一次的添加它们。在传递给
- <literal>Template.process</literal> 的
- 根root对象里的变量将会隐藏同名的共享变量。</para>
-
- <warning>
- <para>如果配置对象在多线程环境中使用,不要使用
- <literal>TemplateModel</literal> 实现类来作为共享变量,
- 因为它是不是<link linkend="gloss.threadSafe">线程安全</link>的!
- 这也是基于Servlet应用程序的典型情形。</para>
- </warning>
-
- <para>出于向后兼容的特性,共享变量的集合初始化时
- (就是对于新的 <literal>Configuration</literal> 实例来说)不能为空。
- 它包含下列用户自定义指令(用户自定义指令使用时需要用
- <literal>@</literal> 来代替<literal>#</literal>):</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>名称</th>
-
- <th>类</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>capture_output</literal></td>
-
- <td><literal>freemarker.template.utility.CaptureOutput</literal></td>
- </tr>
-
- <tr>
- <td><literal>compress</literal></td>
-
- <td><literal>freemarker.template.utility.StandardCompress</literal></td>
- </tr>
-
- <tr>
- <td><literal>html_escape</literal></td>
-
- <td><literal>freemarker.template.utility.HtmlEscape</literal></td>
- </tr>
-
- <tr>
- <td><literal>normalize_newlines</literal></td>
-
- <td><literal>freemarker.template.utility.NormalizeNewlines</literal></td>
- </tr>
-
- <tr>
- <td><literal>xml_escape</literal></td>
-
- <td><literal>freemarker.template.utility.XmlEscape</literal></td>
- </tr>
- </tbody>
- </informaltable>
- </section>
-
- <section xml:id="pgui_config_settings">
- <title>配置设置</title>
-
- <indexterm>
- <primary>setting</primary>
- </indexterm>
-
- <para><emphasis role="term">Settings</emphasis>(配置设置)
- 是影响FreeMarker行为的已被命名的值。配置设置有很多,
- 例如:<literal>locale</literal>,<literal>number_format</literal>,
- <literal>default_encoding</literal>,
- <literal>template_exception_handler</literal>。可以参考 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-">
- <literal>Configuration.setSetting(...)</literal>的Java API 文档</link>
- 来查看配置设置的完整列表。</para>
-
- <para>配置设置存储在 <literal>Configuration</literal> 实例中,可以在
- <literal>Template</literal> 实例中被覆盖。比如,在配置中给
- <literal>locale</literal> 设置为 <literal>"en_US"</literal>,
- 那么使用该配置的所有模板中的
- <literal>locale</literal> 都使用 <literal>"en_US"</literal>,
- 除非在模板中locale被明确地设置成其它不同的值(参见
- <link linkend="ref_directive_include_localized">localization</link>)。
- 因此,在 <literal>Configuration</literal> 中的值充当默认值,
- 这些值在每个模板中也可以被覆盖。在 <literal>Configuration</literal>
- 或 <literal>Template</literal> 实例中的值也可以在单独调用
- <literal>Template.process</literal> 方法后被覆盖。
- 对于每个调用了 <literal>freemarker.core.Environment</literal>
- 对象的值在内部创建时就持有模板执行的运行时环境,也包括了那个级别被覆盖了的设置信息。
- 在模板执行时,那里存储的值也可以被改变,所以模板本身也可以设置配置信息,
- 比如在输出中途来变换 <literal>locale</literal> 设置。</para>
-
- <para>配置信息可以被想象成3层(<literal>Configuration</literal>,
- <literal>Template</literal>,<literal>Environment</literal>),
- 最高层包含特定的值,它为设置信息提供最有效的值。
- 比如(设置信息A到F仅仅是为这个示例而构想的):</para>
-
- <informaltable border="1">
- <col align="left"/>
-
- <col align="center" span="6"/>
-
- <thead>
- <tr>
- <th/>
-
- <th>Setting A</th>
-
- <th>Setting B</th>
-
- <th>Setting C</th>
-
- <th>Setting D</th>
-
- <th>Setting E</th>
-
- <th>Setting F</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td>Layer 3: <literal>Environment</literal></td>
-
- <td>1</td>
-
- <td>-</td>
-
- <td>-</td>
-
- <td>1</td>
-
- <td>-</td>
-
- <td>-</td>
- </tr>
-
- <tr>
- <td>Layer 2: <literal>Template</literal></td>
-
- <td>2</td>
-
- <td>2</td>
-
- <td>-</td>
-
- <td>-</td>
-
- <td>2</td>
-
- <td>-</td>
- </tr>
-
- <tr>
- <td>Layer 1: <literal>Configuration</literal></td>
-
- <td>3</td>
-
- <td>3</td>
-
- <td>3</td>
-
- <td>3</td>
-
- <td>-</td>
-
- <td>-</td>
- </tr>
- </tbody>
- </informaltable>
-
- <para>配置信息的有效值为:A=1,B=2,C=3,D=1,E=2。
- 而F的设置则是 <literal>null</literal>,或者在你获取它的时候将抛出异常。</para>
-
- <para>我们看看如何准确设置配置信息:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>Configuration</literal> 层:
- 原则上设置配置信息时使用 <literal>Configuration</literal>
- 对象的setter方法,例如:</para>
-
- <programlisting role="unspecified">Configuration myCfg = new Configuration(Configuration.VERSION_2_3_23);
-myCfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
-myCfg.setDefaultEncoding("UTF-8");</programlisting>
-
- <para>在真正使用 <literal>Configuration</literal> 对象
- (通常在初始化应用程序时)之前来配置它,后面必须将其视为只读的对象。</para>
-
- <para>在实践中,比如很多Web应用框架中,就应该使用这种框架特定的配置方式来进行配置,
- 比如使用成对的 <literal>String</literal> 来配置(像在
- <literal>.properties</literal> 属性配置文件中那样)。
- 在这种情况下,框架的作者大多数使用 <literal>Configuration</literal>
- 对象的 <literal>setSetting(String name, String value) </literal>方法。
- 这可以参考<link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-"><literal>setSetting</literal>的API文档</link>
- 部分来获取可用的设置名和参数的格式的信息。
- 而在Spring框架中,我们可以这样进行:</para>
-
- <programlisting role="unspecified"><bean id="freemarkerConfig"
- class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
- <property name="freemarkerSettings">
- <props>
- <prop key="incompatible_improvements">2.3.23</prop>
- <prop key="template_exception_handler">rethrow</prop>
- <prop key="default_encoding">UTF-8</prop>
- </props>
- </property>
-</bean></programlisting>
-
- <para>请注意,这种形式的配置( <literal>String</literal> 键-值对)
- 和直接使用 <literal>Configuration</literal> 的API相比,
- 很不幸地被限制了。</para>
- </listitem>
-
- <listitem>
- <para><literal>Template</literal> 层:对于被请求的本地化信息,模板的
- <literal>locale</literal> 设置由
- <literal>Configuration.getTemplate(...)</literal> 来设置。
- 否则,就不能在这里进行设置,除非想控制 <literal>Template</literal>
- 对象来代替 <literal>freemarker.cache.TemplateCache</literal>,这样的话,
- 应该在 <literal>Template</literal> 对象第一次被使用前就设置配置信息,
- 然后就将 <literal>Template</literal> 对象视为是只读的。</para>
- </listitem>
-
- <listitem>
- <para><literal>Environment </literal>层:这里有两种配置方法:</para>
-
- <itemizedlist>
- <listitem>
- <para>使用Java API:使用 <literal>Environment</literal>
- 对象的setter方法。当然想要在模板执行之前来做,然后当调用
- <literal>myTemplate.process(...)</literal> 时会遇到问题,
- 因为在内部创建 <literal>Environment</literal> 对象后立即就执行模板了,
- 导致没有机会来进行设置。这个问题的解决可以用下面两个步骤进行:</para>
-
- <programlisting role="unspecified">Environment env = myTemplate.createProcessingEnvironment(root, out);
-env.setLocale(java.util.Locale.ITALY);
-env.setNumberFormat("0.####");
-env.process(); // process the template</programlisting>
- </listitem>
-
- <listitem>
- <para>在模板中(通常这被认为是不好的做法)直接使用 <link
- linkend="ref.directive.setting"><literal>setting</literal>
- 指令</link>,例如:</para>
-
- <programlisting role="template"><#setting locale="it_IT">
-<#setting number_format="0.####"></programlisting>
- </listitem>
- </itemizedlist>
-
- <para>在这层,当什么时候改变配置信息,是没有限制的。</para>
- </listitem>
- </itemizedlist>
-
- <para>要知道 FreeMarker 支持什么样的配置信息还有它们的意义,
- 可以先看看FreeMarker Java API文档中的下面这部分内容:</para>
-
- <itemizedlist>
- <listitem>
- <para>在三层中 <literal>freemarker.core.Configurable</literal>
- 的setter方法来配置。</para>
- </listitem>
-
- <listitem>
- <para>只在 <literal>Configuration</literal> 层可用的
- <literal>freemarker.template.Configuration</literal>
- 的setter方法来配置。</para>
- </listitem>
-
- <listitem>
- <para>在三层中可用 <literal>String</literal> 键-值对书写的
- <literal>freemarker.core.Configurable.setSetting(String,String)</literal>
- 配置。</para>
- </listitem>
-
- <listitem>
- <para>只在 <literal>Configuration</literal> 层中可用
- <literal>String</literal> 键-值对书写的
- <literal>freemarker.template.Configuration.setSetting(String, String)</literal>
- 配置。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="pgui_config_templateloading">
- <title>模板加载</title>
-
- <indexterm>
- <primary>loading templates</primary>
- </indexterm>
-
- <indexterm>
- <primary>template loading</primary>
- </indexterm>
-
- <indexterm>
- <primary>storing templates</primary>
- </indexterm>
-
- <section>
- <title>模板加载器</title>
-
- <indexterm>
- <primary>template loaders</primary>
- </indexterm>
-
- <para>模板加载器是加载基于抽象模板路径下,比如
- <literal>"index.ftl"</literal> 或
- <literal>"products/catalog.ftl"</literal> 的原生文本数据对象。
- 这由具体的模板加载器对象来确定它们取得请求数据时使用了什么样的数据来源
- (文件夹中的文件,数据等等)。当调用 <literal>cfg.getTemplate</literal>
- (这里的 <literal>cfg</literal> 就是 <literal>Configuration</literal> 实例)时,
- FreeMarker询问模板加载器是否已经为 <literal>cfg</literal>
- 建立返回给定模板路径的文本,之后 FreeMarker 解析文本生成模板。</para>
-
- <section>
- <title>内建模板加载器</title>
-
- <para>在 <literal>Configuration</literal>
- 中可以使用下面的方法来方便建立三种模板加载。
- (每种方法都会在其内部新建一个模板加载器对象,然后创建
- <literal>Configuration</literal> 实例来使用它。)</para>
-
- <programlisting role="unspecified">void setDirectoryForTemplateLoading(File dir);</programlisting>
-
- <para>或</para>
-
- <programlisting role="unspecified">void setClassForTemplateLoading(Class cl, String prefix);</programlisting>
-
- <para>或</para>
-
- <programlisting role="unspecified">void setServletContextForTemplateLoading(Object servletContext, String path);</programlisting>
-
- <para>上述的第一种方法在磁盘的文件系统上设置了一个明确的目录,
- 它确定了从哪里加载模板。不要说可能,<literal>File</literal>
- 参数肯定是一个存在的目录。否则,将会抛出异常。</para>
-
- <para>第二种调用方法使用了一个 <literal>Class</literal>
- 类型的参数和一个前缀。这是让你来指定什么时候通过相同的机制来加载模板,
- 不过是用Java的 <literal>ClassLoader</literal> 来加载类。
- 这就意味着传入的class参数会被 <literal>Class.getResource()</literal>
- 用来调用方法来找到模板。参数 <literal>prefix</literal>
- 是给模板的名称来加前缀的。在实际运行的环境中,
- 类加载机制是首选用来加载模板的方法,通常情况下,从类路径下加载文件的这种机制,
- 要比从文件系统的特定目录位置加载安全而且简单。在最终的应用程序中,
- 所有代码都使用 <literal>.jar</literal> 文件打包也是不错的,
- 这样用户就可以直接执行包含所有资源的 <literal>.jar</literal> 文件了。</para>
-
- <para>第三种调用方式需要Web应用的上下文和一个基路径作为参数,
- 这个基路径是Web应用根路径(<literal>WEB-INF</literal>目录的上级目录)的相对路径。
- 那么加载器将会从Web应用目录开始加载模板。尽管加载方法对没有打包的
- <literal>.war</literal> 文件起作用,因为它使用了
- <literal>ServletContext.getResource()</literal> 方法来访问模板,
- 注意这里我们指的是“目录”。如果忽略了第二个参数(或使用了<literal>""</literal>),
- 那么就可以混合存储静态文件(<literal>.html</literal>,<literal>.jpg</literal>等)
- 和 <literal>.ftl</literal> 文件,只是 <literal>.ftl</literal> 文件可以被送到客户端执行。
- 当然必须在 <literal>WEB-INF/web.xml</literal> 中配置一个Servlet来处理URI格式为
- <literal>*.ftl</literal> 的用户请求,否则客户端无法获取到模板,
- 因此你将会看到Web服务器给出的秘密提示内容。在站点中不能使用空路径,这是一个问题,
- 你应该在 <literal>WEB-INF</literal> 目录下的某个位置存储模板文件,
- 这样模板源文件就不会偶然地被执行到,这种机制对servlet应用程序来加载模板来说,
- 是非常好用的方式,而且模板可以自动更新而不需重启Web应用程序,
- 但是对于类加载机制,这样就行不通了。</para>
- </section>
-
- <section>
- <title>从多个位置加载模板</title>
-
- <para>如果需要从多个位置加载模板,那就不得不为每个位置都实例化模板加载器对象,
- 将它们包装到一个称为 <literal>MultiTemplateLoader</literal> 的特殊模板加载器,
- 最终将这个加载器传递给 <literal>Configuration</literal> 对象的
- <literal>setTemplateLoader(TemplateLoader loader)</literal>方法。
- 下面给出一个使用类加载器从两个不同位置加载模板的示例:</para>
-
- <programlisting role="unspecified">import freemarker.cache.*; // template loaders live in this package
-
-<replaceable>...</replaceable>
-
-FileTemplateLoader ftl1 = new FileTemplateLoader(new File("/tmp/templates"));
-FileTemplateLoader ftl2 = new FileTemplateLoader(new File("/usr/data/templates"));
-ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "");
-TemplateLoader[] loaders = new TemplateLoader[] { ftl1, ftl2, ctl };
-MultiTemplateLoader mtl = new MultiTemplateLoader(loaders);
-
-cfg.setTemplateLoader(mtl);</programlisting>
-
- <para>现在,FreeMarker将会尝试从 <literal>/tmp/templates</literal>
- 目录加载模板,如果在这个目录下没有发现请求的模板,它就会继续尝试从
- <literal>/usr/data/templates</literal> 目录下加载,如果还是没有发现请求的模板,
- 那么它就会使用类加载器来加载模板。</para>
- </section>
-
- <section>
- <title>从其他资源加载模板</title>
-
- <para>如果内建的类加载器都不适合使用,那么就需要来编写自己的类加载器了,
- 这个类需要实现 <literal>freemarker.cache.TemplateLoader</literal> 接口,
- 然后将它传递给 <literal>Configuration</literal> 对象的
- <literal>setTemplateLoader(TemplateLoader loader)</literal>方法。
- 可以阅读API JavaDoc文档获取更多信息。</para>
-
- <para>如果模板需要通过URL访问其他模板,那么就不需要实现
- <literal>TemplateLoader</literal> 接口了,可以选择子接口
- <literal>freemarker.cache.URLTemplateLoader</literal> 来替代,
- 只需实现 <literal>URL getURL(String templateName)</literal> 方法即可。</para>
- </section>
-
- <section>
- <title>模板名称(模板路径)</title>
-
- <indexterm>
- <primary>path</primary>
- </indexterm>
-
- <indexterm>
- <primary>template path</primary>
- </indexterm>
-
- <indexterm>
- <primary>template name</primary>
- </indexterm>
-
- <para>解析模板的名称(也就是模板路径)是由模板解析器来决定的。
- 但是要和其它对路径的格式要求很严格的组件一起使用。通常来说,
- 强烈建议模板加载器使用URL风格的路径。
- 在URL路径(或在UN*X路径)中符号有其它含义时,那么路径中不要使用
- <literal>/</literal>(路径分隔符)字符,<literal>.</literal>
- (同目录符号)和<literal>..</literal>(父目录符号)。字符
- <literal>*</literal>(星号)是被保留的,
- 它用于FreeMarker的 <quote>模板获取</quote> 特性。</para>
-
- <para><literal>://</literal>(或者使用
- <literal>template_name_format</literal> 配置设置到
- <literal>DEFAULT_2_4_0</literal>,<literal>:</literal> (冒号)
- 字符)是被保留用来指定体系部分的,和URI中的相似。比如
- <literal>someModule://foo/bar.ftl</literal> 使用
- <literal>someModule</literal>,或者假定 <literal>DEFAULT_2_4_0</literal>
- 格式,<literal>classpath:foo/bar.ftl</literal> 使用
- <literal>classpath</literal> 体系。解释体系部分完全由
- <literal>TemplateLoader</literal> 决定。
- (FreeMarker核心仅仅知道体系的想法,否则它不能正常处理相对模板名称。)</para>
-
- <para>FreeMarker通常在将路径传递到 <literal>TemplateLoader</literal>
- 之前把它们正常化,所以路径中不会包含 <literal>/../</literal> 这样的内容,
- 路径会相对于虚构的模板根路径(也就是它们不会以 <literal>/</literal> 开头)。
- 其中也不会包含 <literal>*</literal>,因为模板获取发生在很早的阶段。
- 此外,将 <literal>template_name_format</literal> 设置为
- <literal>DEFAULT_2_4_0</literal>,多个连续的 <literal>/</literal>
- 将会被处理成单独的 <literal>/</literal>
- (除非它们是 <literal>://</literal> 模式分隔符的一部分)。</para>
-
- <para>请注意,不管主机运行的操作系统是什么,
- FreeMarker 模板加载时经常使用斜线(而不是反斜线)。</para>
- </section>
- </section>
-
- <section xml:id="pgui_config_templateloading_caching">
- <title>模板缓存</title>
-
- <indexterm>
- <primary>caching</primary>
- </indexterm>
-
- <indexterm>
- <primary>template caching</primary>
- </indexterm>
-
- <para>FreeMarker 是会缓存模板的(假设使用 <literal>Configuration</literal>
- 对象的方法来创建 <literal>Template</literal> 对象)。这就是说当调用
- <literal>getTemplate</literal>方法时,FreeMarker不但返回了
- <literal>Template</literal> 对象,而且还会将它存储在缓存中,
- 当下一次再以相同(或相等)路径调用 <literal>getTemplate</literal> 方法时,
- 那么它只返回缓存的 <literal>Template</literal> 实例,
- 而不会再次加载和解析模板文件了。</para>
-
- <para>如果更改了模板文件,当下次调用模板时,FreeMarker 将会自动重新载入和解析模板。
- 然而,要检查模板文件是否改变内容了是需要时间的,有一个
- <literal>Configuration</literal> 级别的设置被称作"更新延迟",它可以用来配置这个时间。
- 这个时间就是从上次对某个模板检查更新后,FreeMarker再次检查模板所要间隔的时间。
- 其默认值是5秒。如果想要看到模板立即更新的效果,那么就要把它设置为0。
- 要注意某些模板加载器也许在模板更新时可能会有问题。
- 例如,典型的例子就是在基于类加载器的模板加载器就不会注意到模板文件内容的改变。</para>
-
- <para>当调用了 <literal>getTemplate</literal> 方法时,
- 与此同时FreeMarker意识到这个模板文件已经被移除了,所以这个模板也会从缓存中移除。
- 如果Java虚拟机认为会有内存溢出时,默认情况它会从缓存中移除任意模板。
- 此外,你还可以使用 <literal>Configuration</literal> 对象的
- <literal>clearTemplateCache</literal> 方法手动清空缓存。</para>
-
- <para>何时将一个被缓存了的模板清除的实际应用策略是由配置的属性
- <literal>cache_storage</literal> 来确定的,通过这个属性可以配置任何
- <literal>CacheStorage</literal> 的实现。对于大多数用户来说,
- 使用 <literal>freemarker.cache.MruCacheStorage</literal> 就足够了。
- 这个缓存存储实现了二级最近使用的缓存。在第一级缓存中,
- 组件都被强烈引用到特定的最大数目(引用次数最多的组件不会被Java虚拟机抛弃,
- 而引用次数很少的组件则相反)。当超过最大数量时,
- 最近最少使用的组件将被送至二级缓存中,在那里它们被很少引用,
- 直到达到另一个最大的数目。引用强度的大小可以由构造方法来指定。
- 例如,设置强烈部分为20,轻微部分为250:</para>
-
- <programlisting role="unspecified">cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))</programlisting>
-
- <para>或者,使用 <literal>MruCacheStorage</literal> 缓存,
- 它是默认的缓存存储实现:</para>
-
- <programlisting role="unspecified">cfg.setSetting(Configuration.CACHE_STORAGE_KEY, "strong:20, soft:250");</programlisting>
-
- <para>当创建了一个新的 <literal>Configuration</literal> 对象时,
- 它使用一个 <literal>strongSizeLimit</literal> 值为0的
- <literal>MruCacheStorage</literal> 缓存来初始化,
- <literal>softSizeLimit</literal> 的值是 <literal>Integer.MAX_VALUE</literal>
- (也就是在实际中,是无限大的)。但是使用非0的 <literal>strongSizeLimit</literal>
- 对于高负载的服务器来说也许是一个更好的策略,对于少量引用的组件来说,
- 如果资源消耗已经很高的话,Java虚拟机往往会引发更高的资源消耗,
- 因为它不断从缓存中抛出经常使用的模板,这些模板还不得不再次加载和解析。</para>
- </section>
- </section>
-
- <section xml:id="pgui_config_errorhandling">
- <title>错误控制</title>
-
- <indexterm>
- <primary>error handling</primary>
- </indexterm>
-
- <indexterm>
- <primary>exception handling</primary>
- </indexterm>
-
- <section>
- <title>可能的异常</title>
-
- <para>关于 FreeMarker 发生的异常,可以分为如下几类:</para>
-
- <itemizedlist>
- <listitem>
- <para>当配置 FreeMarker 时发生异常:典型地情况,就是在应用程序初始化时,
- 仅仅配置了一次 FreeMarker。在这个过程中,异常就会发生,
- 从 FreeMarker 的API中,我们可以很清楚的看到这一点...</para>
- </listitem>
-
- <listitem>
- <para>当加载和解析模板时发生异常:调用了
- <literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal> 方法,
- FreeMarker就要把模板文件加载到内存中然后来解析它
- (除非模板已经在 <literal>Configuration</literal> 对象中被 <link
- linkend="pgui_config_templateloading_caching">缓存</link> 了)。
- 在这期间,有两种异常可能发生:</para>
-
- <itemizedlist>
- <listitem>
- <para>因模板文件没有找到而发生的 <literal>IOException</literal> 异常,
- 或在读取文件时发生其他的I/O问题。比如没有读取文件的权限,或者是磁盘错误。
- 这些错误的发出者是<link linkend="pgui_config_templateloading">
- <literal>TemplateLoader</literal> 对象</link>,可以将它作为插件设置到
- <literal>Configuration</literal>对象中。(为了正确起见:这里所说的”文件”,
- 是简化形式。例如,模板也可以存储在关系型数据库的表中。这是
- <literal>TemplateLoader</literal>所要做的事。)</para>
- </listitem>
-
- <listitem>
- <para>根据FTL语言的规则,模板文件发生语法错误时会导致
- <literal>freemarker.core.ParseException</literal>异常。当获得
- <literal>Template</literal>对象
- (<literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal>)时,
- 这种错误就会发生,而不是当执行
- (<literal>Template.process(<replaceable>...</replaceable>)</literal>)模板的时候。
- 这种异常是 <literal>IOException</literal> 的一个子类。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>当执行(处理)模板时发生的异常,也就是当调用了
- <literal>Template.process(<replaceable>...</replaceable>)</literal> 方法时会发生的两种异常:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>当试图写入输出对象时发生错误而导致的 <literal>IOException</literal> 异常。</para>
- </listitem>
-
- <listitem>
- <para>当执行模板时发生的其它问题而导致的
- <literal>freemarker.template.TemplateException</literal> 异常。
- 比如,一个频繁发生的错误,就是当模板引用一个不存在的变量。
- 默认情况下,当 <literal>TemplateException</literal> 异常发生时,
- FreeMarker会用普通文本格式在输出中打印出FTL的错误信息和堆栈跟踪信息,
- 然后再次抛出 <literal>TemplateException</literal> 异常而中止模板的执行,
- 就可以捕捉到 <literal>Template.process(<replaceable>...</replaceable>)</literal>
- 方法抛出的异常了。而这种行为是可以定制的。FreeMarker也会经常写
- <literal>TemplateException</literal> 异常的
- <link linkend="pgui_misc_logging">日志</link>。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>根据TemplateException来自定义处理方式</title>
-
- <para><literal>TemplateException</literal> 异常在模板处理期间的抛出是由
- <literal>freemarker.template.TemplateExceptionHandler</literal>
- 对象控制的,这个对象可以使用
- <literal>setTemplateExceptionHandler(<replaceable>...</replaceable>)</literal>
- 方法配置到 <literal>Configuration</literal> 对象中。
- <literal>TemplateExceptionHandler</literal> 对象只包含一个方法:</para>
-
- <programlisting role="unspecified">void handleTemplateException(TemplateException te, Environment env, Writer out)
- throws TemplateException;</programlisting>
-
- <para>无论 <literal>TemplateException</literal>
- 异常什么时候发生,这个方法都会被调用。
- 异常处理是传递的 <literal>te</literal> 参数控制的,
- 模板处理的运行时(Runtime,译者注)环境可以访问 <literal>env</literal> 变量,
- 处理器可以使用 <literal>out</literal> 变量来打印输出信息。
- 如果方法抛出异常(通常是重复抛出 <literal>te</literal>),那么模板的执行就会中止,
- 而且 <literal>Template.process(<replaceable>...</replaceable>)</literal>
- 方法也会抛出同样的异常。如果 <literal>handleTemplateException</literal>
- 对象不抛出异常,那么模板将会继续执行,就好像什么也没有发生过一样,
- 但是引发异常的语句将会被跳过(后面会详细说)。
- 当然,控制器仍然可以在输出中打印错误提示信息。</para>
-
- <para>任何一种情况下,当 <literal>TemplateExceptionHandler</literal> 被调用前,
- FreeMarker 将会记录异常<link linkend="pgui_misc_logging">日志</link>。</para>
-
- <para>我们用实例来看一下,当错误控制器不抛出异常时,
- FreeMarker是如何跳过出错''语句''的。假设我们已经使用了如下模板异常控制器:</para>
-
- <programlisting role="unspecified">class MyTemplateExceptionHandler implements TemplateExceptionHandler {
- public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out)
- throws TemplateException {
- try {
- out.write("[ERROR: " + te.getMessage() + "]");
- } catch (IOException e) {
- throw new TemplateException("Failed to print error message. Cause: " + e, env);
- }
- }
-}
-
-<replaceable>...</replaceable>
-
-cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());</programlisting>
-
- <para>如果错误发生在非FTL标记(没有被包含在
- <literal><#<replaceable>...</replaceable>></literal> 或
- <literal><@<replaceable>...</replaceable>></literal>之间)的插值中,
- 那么整个插值将会被跳过。那么下面这个模板
- (假设 <literal>badVar</literal> 在数据模型中不存在):</para>
-
- <programlisting role="template">a${badVar}b</programlisting>
-
- <para>如果我们使用了 <literal>MyTemplateExceptionHandler</literal>,就会打印:</para>
-
- <programlisting role="output">a[ERROR: Expression badVar is undefined on line 1, column 4 in test.ftl.]b</programlisting>
-
- <para>而下面这个模板也会打印相同信息(除了报错的列数位置会不同...):</para>
-
- <programlisting role="template">a${"moo" + badVar}b</programlisting>
-
- <para>因为像这样来写时,只要插值内发生任何错误,整个插值都会被跳过。</para>
-
- <para>如果错误发生在指令调用中参数的计算时,或者是指令参数列表发生问题时,
- 或在<literal><@<replaceable>exp</replaceable>
- <replaceable>...</replaceable>></literal>中计算
- <literal><replaceable>exp</replaceable></literal>时发生错误,或者
- <literal><replaceable>exp</replaceable></literal>不是用户自定义的指令,
- 那么整个指令调用都会被跳过。例如:</para>
-
- <programlisting role="template">a<#if badVar>Foo</#if>b</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">a[ERROR: Expression badVar is undefined on line 1, column 7 in test.ftl.]b</programlisting>
-
- <para>请注意,错误发生在 <literal>if</literal> 指令的开始标签
- (<literal><#if badVar></literal>)中,但是整个指令的调用都被跳过了。
- 从逻辑上说,嵌套的内容(<literal>Foo</literal>)也被跳过了,
- 因为嵌套的内容是受被包含的指令(<literal>if</literal>)控制(打印)的。</para>
-
- <para>下面这个的输出也是相同的(除了报错的列数会不同...):</para>
-
- <programlisting role="template">a<#if "foo${badVar}" == "foobar">Foo</#if>b</programlisting>
-
- <para>因为,正如这样来写,在参数处理时发生任何一个错误,
- 整个指令的调用都将会被跳过。</para>
-
- <para>如果错误发生在已经开始执行的指令之后,那么指令调用将不会被跳过。
- 也就是说,如果在嵌套的内容中发生任何错误:</para>
-
- <programlisting role="template">a
-<#if true>
- Foo
- ${badVar}
- Bar
-</#if>
-c</programlisting>
-
- <para>或者在一个宏定义体内:</para>
-
- <programlisting role="template">a
-<@test />
-b
-<#macro test>
- Foo
- ${badVar}
- Bar
-</#macro></programlisting>
-
- <para>那么输出将会是:</para>
-
- <programlisting role="output">a
- Foo
- [ERROR: Expression badVar is undefined on line 4, column 5 in test.ftl.]
- Bar
-c</programlisting>
-
- <para>FreeMarker 本身带有这些预先编写的错误控制器:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>TemplateExceptionHandler.DEBUG_HANDLER</literal>:
- 打印堆栈跟踪信息(包括FTL错误信息和FTL堆栈跟踪信息)和重新抛出的异常。
- 这是默认的异常控制器(也就是说,在所有新的 <literal>Configuration</literal>
- 对象中,它是初始化好的)。</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateExceptionHandler.HTML_DEBUG_HANDLER</literal>:
- 和 <literal>DEBUG_HANDLER</literal> 相同,但是它可以格式化堆栈跟踪信息,
- 那么就可以在Web浏览器中来阅读错误信息。
- 当你在制作HTML页面时,建议使用它而不是 <literal>DEBUG_HANDLER</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateExceptionHandler.IGNORE_HANDLER</literal>:
- 简单地压制所有异常(但是要记住,FreeMarker 仍然会写日志)。
- 它对处理异常没有任何作用,也不会重新抛出异常。</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateExceptionHandler.RETHROW_HANDLER</literal>:
- 简单重新抛出所有异常而不会做其它的事情。
- 这个控制器对Web应用程序(假设你在发生异常之后不想继续执行模板)来说非常好,
- 因为它在生成的页面发生错误的情况下,给了你很多对Web应用程序的控制权
- (因为FreeMarker不向输出中打印任何关于该错误的信息)。
- 要获得更多在Web应用程序中处理错误的信息,可以
- <link linkend="misc.faq.niceErrorPage">参见FAQ</link>。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>在模板中明确地处理错误</title>
-
- <para>尽管它和 FreeMarker 的配置(本章的主题)无关,但是为了说明的完整性,
- 在这里提及一下,你可以在模板中直接控制错误。
- 通常这不是一个好习惯(尽量保持模板简单,技术含量不要太高),但有时仍然需要:</para>
-
- <itemizedlist>
- <listitem>
- <para>处理不存在/为空的变量: <xref
- linkend="dgui_template_exp_missing"/></para>
- </listitem>
-
- <listitem>
- <para>在发生障碍的"porlets"中留存下来,还可以扩展参考: <xref linkend="ref_directive_attempt"/></para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="pgui_config_incompatible_improvements">
- <title>"不兼容改进"设置</title>
-
- <indexterm>
- <primary>incompatible improvements</primary>
- </indexterm>
-
- <indexterm>
- <primary>incompatible_improvements</primary>
- </indexterm>
-
- <indexterm>
- <primary>incompatibleImprovements</primary>
- </indexterm>
-
- <indexterm>
- <primary>backward compatibility</primary>
- </indexterm>
-
- <section>
- <title>它要做什么</title>
-
- <para>该设置指定了 FreeMarker 的版本号,那么就不会100%向后兼容bug修复和改进
- <emphasis>你想要启用</emphasis> 已经实现的内容。
- 通常来说,默认把它留在2.3.0(最大向后兼容版本)是一个坏主意。</para>
-
- <para>在新项目中,应该将它设置为实际使用的FreeMarker版本号。
- 而在老项目中,那么最好也将它设置的高一些,最好检查一下哪些修改是激活状态(可以在 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#Configuration-freemarker.template.Version-"><literal>Configuration(Version)</literal>
- 构造方法的API JavaDoc文档</link> 中找到它们),至少不仅仅是
- <quote>不兼容改进</quote> 第三版本号(小版本)设置提高。通常来讲,
- 只要为该设置增加最后的版本号,那么这些修改的风险就会小很多。</para>
-
- <para>Bug修复和改进是完全向后兼容的,同样,它们也是重要的安全更新,
- 不管 <quote>不兼容改进</quote> 如何设置都是启用的。</para>
-
- <para>该设置的一个重要结果是应用程序可以检查声明的 FreeMarker 最小版本需求是否达到。
- 比如你设置了2.3.22,但是应用程序却意外部署到了 FreeMarker 2.3.21,
- 那么FreeMarker就会出问题,告诉你需要一个更高的版本。
- 最后,请求的修复/改进不会作用于低版本。</para>
- </section>
-
- <section xml:id="pgui_config_incompatible_improvements_how_to_set">
- <title>如何设置</title>
-
- <para>这个不兼容改进的设置在 <literal>Configuration</literal> 级别。
- 它可以在多种方式下设置(假设我们想将它设置为2.3.22):</para>
-
- <itemizedlist>
- <listitem>
- <para>创建
- <literal>freemarker.template.Configuration</literal> 对象,比如:</para>
-
- <programlisting role="unspecified">... = new Configuration(Configuration.VERSION_2_3_22)</programlisting>
- </listitem>
-
- <listitem>
- <para>或者,使用初始化设置来更改 <literal>Configuration</literal> 单例,比如:</para>
-
- <programlisting role="unspecified">cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_22)</programlisting>
- </listitem>
-
- <listitem>
- <para>或者,如果使用properties文件来配置FreeMarker
- (<literal>*.properties</literal> 文件或者
- <literal>java.util.Properties</literal> 对象),添加:</para>
-
- <programlisting role="unspecified">incompatible_improvements=2.3.22</programlisting>
- </listitem>
-
- <listitem>
- <para>或者,如果通过 <literal>FreemarkerServlet</literal>
- 来配置FreeMarker,那么将 <literal>init-param</literal>
- 添加到 <literal>web.xml</literal> 中:</para>
-
- <programlisting role="unspecified"><init-param>
- <param-name>incompatible_improvements</param-name>
- <param-value>2.3.22</param-value>
-</init-param></programlisting>
- </listitem>
- </itemizedlist>
-
- <para>但是, 在应用程序中 <emphasis>如果设置了
- <literal>object_wrapper</literal></emphasis>
- (也就是 <literal>Configuration.setObjectWrapper(ObjectWrapper)</literal>),
- 那么要知道很重要的一点,<literal>BeansWrapper</literal> 和它的子类(最重要的是,
- <literal>DefaultObjectWrapper</literal>) 有它们自己独立的
- <literal>incompatibleImprovements</literal> 属性,还有一些修复/改进会被它激活,
- 而不是通过 <literal>Configuration</literal> 的相似设置。
- 如果你没有在任何地方设置过 <literal>object_wrapper</literal>,那么不需要知道这点,
- 因为,和 <literal>Configuration</literal> 一样,
- 默认的 <literal>object_wrapper</literal> 也有相同的 <quote>不兼容改进</quote>。
- 但是,如果设置了 <literal>object_wrapper</literal>,
- 那么就不要忘了设置 <literal>ObjectWrapper</literal> 自己的
- <literal>incompatibleImprovements</literal> 属性,此外还有
- <literal>Configuration</literal>。(请注意,对于 <literal>Configuration</literal> 来说,
- 有不同的 <quote>不兼容改进</quote> 也是可以的,而对于 <literal>ObjectWrapper</literal>,
- 则有它自己的选择。) 至于 <literal>DefaultObjectWrapper</literal>
- (对于 <literal>BeansWrapper</literal> 也是一样的,只是不同的类名而已) <link
- linkend="topic.setDefaultObjectWrapperIcIIndividually">参看这里如何来设置它</link>。</para>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="pgui_misc">
- <title>其它</title>
-
- <para>这只是一个介绍性的导引,可以查看 <olink
- targetdoc="api">FreeMarker Java API 文档</olink> 获取详细信息。</para>
-
- <section xml:id="pgui_misc_var">
- <title>变量,范围</title>
-
- <indexterm>
- <primary>variables</primary>
- </indexterm>
-
- <indexterm>
- <primary>variable scopes</primary>
- </indexterm>
-
- <indexterm>
- <primary>scopes</primary>
- </indexterm>
-
- <para>本章介绍当模板在访问变量时发生了什么事情,还有变量是如何存储的。</para>
-
- <para>当调用 <literal>Template.process</literal> 方法时,它会在方法内部创建一个
- <literal>Environment</literal> 对象,在 <literal>process</literal> 返回之前一直使用。
- 该对象存储模板执行时的运行状态信息。除了这些,它还存储由模板中指令,如
- <literal>assign</literal>, <literal>macro</literal>,
- <literal>local</literal> 或 <literal>global</literal> 创建的变量。
- 它不会尝试修改传递给 <literal>process</literal> 的数据模型对象,
- 也不会创建或替换存储在配置中的共享变量。</para>
-
- <para>当你想要读取一个变量时,FreeMarker 将会以这种顺序来查找,
- 直到发现了完全匹配的的变量名称才会停下来:</para>
-
- <orderedlist>
- <listitem>
- <para>在 Environment 中:</para>
-
- <orderedlist>
- <listitem>
- <para>如果在循环中,在循环变量的集合中。
- 循环变量是由如 <literal>list</literal> 等指令来创建的。</para>
- </listitem>
-
- <listitem>
- <para>如果在宏中,在宏的局部变量集合中。
- 局部变量可以由 <literal>local</literal> 指令创建。
- 而且,宏的参数也是局部变量。</para>
- </listitem>
-
- <listitem>
- <para>在当前的 <link
- linkend="dgui_misc_namespace">命名空间</link> 中。
- 可以使用 <literal>assign</literal>
- 指令将变量放到一个命名空间中。</para>
- </listitem>
-
- <listitem>
- <para>在由 <literal>global</literal> 指令创建的变量集合中。
- FTL将它们视为数据模型的普通成员变量一样来控制它们。
- 也就是说,它们在所有的命名空间中都可见,
- 你也可以像访问一个数据模型中的数据一样来访问它们。</para>
- </listitem>
- </orderedlist>
- </listitem>
-
- <listitem>
- <para>在传递给 <literal>process</literal> 方法的数据模型对象中。</para>
- </listitem>
-
- <listitem>
- <para>在 <literal>Configuration</literal> 对象存储的共享变量集合中。</para>
- </listitem>
- </orderedlist>
-
- <para>在实际操作中,来自模板设计者的观点是这6种情况应该只有4种,
- 因为从那种观点来看,后面3种(由 <literal>global</literal> 创建的变量,
- 真实的数据模型对象,共享变量)共同构成了全局变量的集合。</para>
-
- <para>请注意,在FTL中可以从一个特定层面获取 <link
- linkend="ref_specvar">特定变量</link>。</para>
- </section>
-
- <section xml:id="pgui_misc_charset">
- <title>字符集问题</title>
-
- <indexterm>
- <primary>charset</primary>
- </indexterm>
-
- <indexterm>
- <primary>encoding</primary>
- </indexterm>
-
- <para>像其它大多数的Java应用程序一样,FreeMarker使用 "<link
- linkend="gloss.unicode">UNICODE</link> 文本"(UTF-16)来工作。
- 不过,也有必须处理 <link linkend="gloss.charset">字符集</link> 的情况,
- 因为它不得不和外界交换数据,这就会使用到很多字符集。</para>
-
- <section>
- <title>输入的字符集</title>
-
- <para>当 FreeMarker 要加载模板文件(或没有解析的文本文件)时,
- 那就必须要知道文件使用的字符集,因为文件的存储是原生的字节序列。
- 可以使用 <literal>encoding</literal> <link
- linkend="pgui_config_settings">配置</link> 来确定字符集。
- 这个配置项只在 FreeMarker 使用 <literal>Configuration</literal>
- 对象的 <literal>getTemplate</literal>
- 方法加载模板(解析过的或没有解析过的)时起作用。请注意 <link
- linkend="ref.directive.include"><literal>include</literal> 指令 </link>
- 在内部也使用了这个方法,所以 <literal>encoding</literal>
- 的值对一个已经加载的模板,而且如果这个模板包含 <literal>include</literal>
- 指令的调用来说很重要。</para>
-
- <para><literal>encoding</literal> 配置的getter和setter方法在第一个(配置)层面很特殊。
- getter方法猜想返回值是基于 <literal>Locale</literal>(本地化,译者注)传递的参数;
- 它在地图区域编码表(称为编码地图)中查询编码,如果没有找到该区域,就返回默认编码。
- 可以使用配置对象的 <literal>setEncoding(Locale locale, String encoding)</literal>
- 方法来填充编码表;编码表初始化时是空的。默认的初始编码是系统属性
- <literal>file.encoding</literal> 的值,但是可以通过
- <literal>setDefaultEncoding</literal> 方法来设置一个不同的默认值,而不是依赖它。
- 对于新项目来说,默认的编码设置就是 <literal>utf-8</literal>。</para>
-
- <para>也可以在模板层或运行环境层(当指定编码值作为 <literal>getTemplate</literal>
- 方法的参数时,应该在模板层覆盖 <literal>encoding</literal> 设置)直接给定值来覆盖
- <literal>encoding</literal> 的设置。如果不覆盖它,那么 <literal>locale</literal>
- 设置的有效值将会是 <literal>configuration.getEncoding(Locale)</literal> 方法的返回值。</para>
-
- <para>而且,代替这种基于字符集猜测的机制,也可以在模板文件中使用
- <link linkend="ref.directive.ftl"><link
- linkend="ref.directive.ftl"><literal>ftl</literal></link>
- 指令</link>, 比如 <literal><#ftl
- encoding="utf-8"></literal> 来指定特定的字符集。</para>
-
- <para>请注意,模板使用的字符集和模板生成的输出内容的字符集是独立的
- (除非包含 FreeMarker 的软件故意将设置输出内容的字符集和模板字符集设置成相同的)。</para>
- </section>
-
- <section>
- <title>输出的字符集</title>
-
- <note>
- <para><literal>output_encoding</literal> 设置/参数和 <link
- linkend="ref_builtin_url">内建函数<literal>url</literal></link>
- 从FreeMarker 2.3.1版本开始才可以使用,而在2.3以前的版本中是不存在的。</para>
- </note>
-
- <para><indexterm>
- <primary>output encoding</primary>
- </indexterm><indexterm>
- <primary>output charset</primary>
- </indexterm>原则上,FreeMarker 不处理输出内容的字符集问题,
- 因为 FreeMarker 将输出内容都写入了 <literal>java.io.Writer</literal>
- 对象中。而 <literal>Writer</literal>
- 对象是由封装了 FreeMarker(比如Web应用框架) 的软件生成的,
- 那么输出内容的字符集就是由封装软件来控制的。而FreeMarker有一个称为
- <literal>output_encoding</literal>(开始于 FreeMarker 2.3.1 版本之后)的设置。
- 封装软件应该使用这个设置(<literal>Writer</literal>对象使用的字符集)
- 来通知 FreeMarker 在输出中(否则 FreeMarker 不能找到它)使用哪种字符集。
- 有一些新的特性,如<link
- linkend="ref_builtin_url">内建函数<literal>url</literal></link>,<link
- linkend="ref_specvar">特殊变量<literal>output_encoding</literal></link>
- 也利用这个信息。因此,如果封装软件没有设置字符集这个信息,
- 那么 FreeMarker 需要知道输出字符集的特性就不能被利用了。</para>
-
- <para>如果你使用 FreeMarker 来编写软件,
- 你也许想知道在输出内容中到底选择了哪种字符集。
- 当然这取决于运行 FreeMarker 输出内容的计算机本身,
- 但是如果用户对这个问题可以变通,
- 那么通用的实践是使用模板文件的字符集作为输出的字符集,或者使用UTF-8。
- 通常使用UTF-8是最佳的实践,因为任意的文本可能来自数据模型,
- 那就可能包含不能被模板字符集所编码的字符。</para>
-
- <para>如果使用了
- <literal>Template.createProcessingEnvironment(<replaceable>...</replaceable>)</literal>
- 和 <literal>Environment.process(<replaceable>...</replaceable>)</literal> 方法来代替
- <literal>Template.process(<replaceable>...</replaceable>)</literal> 方法,
- FreeMarker 的设置可以对任意独立执行的模板进行。
- 因此,你可以对每个独立执行的模板设置 <literal>output_encoding</literal> 信息:</para>
-
- <programlisting role="unspecified">Writer w = new OutputStreamWriter(out, outputCharset);
-Environment env = template.createProcessingEnvironment(dataModel, w);
-env.setOutputEncoding(outputCharset);
-env.process();</programlisting>
- </section>
- </section>
-
- <section xml:id="pgui_misc_multithreading">
- <title>多线程</title>
-
- <indexterm>
- <primary>multithreading</primary>
- </indexterm>
-
- <indexterm>
- <primary>thread-safety</primary>
- </indexterm>
-
- <para>在多线程运行环境中, <literal>Configuration</literal> 实例,
- <literal>Template</literal> 实例和数据模型应该是永远不能改变(只读)的对象。
- 也就是说,创建和初始化它们(如使用 <literal>set<replaceable>...</replaceable></literal>
- 方法)之后,就不能再修改它们了(比如不能再次调用
- <literal>set<replaceable>...</replaceable></literal> 方法)。
- 这就允许我们在多线程环境中避免代价很大的同步锁问题。要小心 <literal>Template</literal> 实例;
- 当使用了 <literal>Configuration.getTemplate</literal> 方法获得
- <literal>Template</literal> 一个实例时,也许得到的是从模板缓存中缓存的实例,
- 这些实例都已经被其他线程使用了,所以不要调用它们的
- <literal>set<replaceable>...</replaceable></literal> 方法
- (当然调用 <literal>process</literal> 方法还是不错的)。</para>
-
- <para>如果只从 <emphasis>同一个</emphasis> 独立线程中访问所有对象,
- 那么上面所述的限制将不会起作用。</para>
-
- <para>使用FTL来修改数据模型对象或者 <link
- linkend="pgui_config_sharedvariables">共享变量</link> 是不太可能的,
- 除非将方法(或其他对象)放到数据模型中来做。
- 我们不鼓励你编写修改数据模型对象或共享变量的方法。多试试使用存储在环境对象
- (这个对象是为独立的 <literal>Template.process</literal> 调用而创建的,
- 用来存储模板处理的运行状态)中的变量,所以最好不要修改那些由多线程使用的数据。
- 要获取更多信息,请阅读:<xref linkend="pgui_misc_var"/></para>
- </section>
-
- <section xml:id="pgui_misc_beanwrapper">
- <title>Bean的包装</title>
-
- <indexterm>
- <primary>wrapping</primary>
-
- <secondary>reflection</secondary>
- </indexterm>
-
- <indexterm>
- <primary>wrapping</primary>
-
- <secondary>beans</secondary>
- </indexterm>
-
- <indexterm>
- <primary>beans</primary>
-
- <secondary>wrapping</secondary>
- </indexterm>
-
- <indexterm>
- <primary>BeansWrapper</primary>
- </indexterm>
-
- <note>
- <para>直接使用 <literal>BeansWrapper</literal> 是不推荐的。
- 而可以使用它的子类 <literal>DefaultObjectWrapper</literal>
- 来代替,要确保它的 <literal>incompatibleImprovements</literal>
- 属性至少是2.3.22。<literal>DefaultObjectWrapper</literal>
- 给出了干净的数据模型(很少的容易混淆的多类型的FTL值)而且通常速度很快。<link
- linkend="pgui_datamodel_defaultObjectWrapper">关于更多
- DefaultObjectWrapper 可以参看这里...</link></para>
- </note>
-
- <para><literal>freemarker.ext.beans.BeansWrapper</literal> 是一个<link
- linkend="pgui_datamodel_objectWrapper">对象包装器</link>,
- 最初加到FreeMarker中是为了将任意的POJO(Plan Old Java Objects,普通的Java对象)
- 包装成 <literal>TemplateModel</literal> 接口类型。
- 这样它就可以以正常的方式来进行处理,事实上 <literal>DefaultObjectWrapper</literal>
- 本身是 <literal>BeansWrapper</literal> 的扩展类。
- 这里描述的所有东西对 <literal>DefaultObjectWrapper</literal> 都是适用的,
- 除了 <literal>DefaultObjectWrapper</literal> 会用到
- <literal>freemarker.template.Simple<replaceable>Xxx</replaceable></literal>
- 类包装的<literal>String</literal>,<literal>Number</literal>,
- <literal>Date</literal>,<literal>array</literal>,
- <literal>Collection</literal>(如<literal>List</literal>),
- <literal>Map</literal>,<literal>Boolean</literal> 和
- <literal>Iterator</literal>对象,会用 <literal>freemarker.ext.dom.NodeModel</literal>
- 来包装W3C的DOM结点(<link linkend="xgui">更多关于包装的W3C DOM 可以参考这里...</link>),
- 所以上述这些描述的规则不适用。</para>
-
- <para>当出现下面这些情况时,
- 你会想使用 <literal>BeansWrapper</literal> 包装器来代替
- <literal>DefaultObjectWrapper</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para>在模板执行期间,数据模型中的 <literal>Collection</literal>
- 和 <literal>Map</literal> 应该被允许修改。
- (<literal>DefaultObjectWrapper</literal> 会阻止这样做,
- 因为当它包装对象时创建了数据集合的拷贝,而这些拷贝都是只读的。)</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>array</literal>,<literal>Collection</literal>
- 和 <literal>Map</literal> 对象的标识符当在模板中被传递到被包装对象的方法时,
- 必须被保留下来。也就是说,那些方法必须得到之前包装好的同类对象。</para>
- </listitem>
-
- <listitem>
- <para>如果在之前列出的Java API中的类(如 <literal>String</literal>,
- <literal>Map</literal>,<literal>List</literal> 等)应该在模板中可见。
- 还有 <literal>BeansWrapper</literal>,默认情况下它们是不可见的,
- 但是可以设置获取的可见程度(后面将会介绍)。请注意这是一个不好的实践,
- 尽量去使用 <link linkend="ref_builtins">内建函数</link>
- (如<literal>foo?size</literal>,<literal>foo?upper_case</literal>,
- <literal>foo?replace('_', '-')</literal> 等)来代替Java API的使用。</para>
- </listitem>
- </itemizedlist>
-
- <para>下面是对 <literal>BeansWrapper</literal> 创建的
- <literal>TemplateModel</literal> 对象进行的总结。为了后续的讨论,
- 这里我们假设在包装之前对象都称为 <literal>obj</literal>,
- 而包装的后称为 <literal>model</literal>。</para>
-
- <section xml:id="beanswrapper_hash">
- <title>模板哈希表模型功能(TemplateHashModel functionality)</title>
-
- <para>所有的对象都将被包装成 <literal>TemplateHashModel</literal> 类型,
- 进而可以获取出JavaBean对象中的属性和方法。这样,
- 就可以在模板中使用 <literal>model.foo</literal> 的形式来调用
- <literal>obj.getFoo()</literal> 方法或 <literal>obj.isFoo()</literal> 方法了。
- (要注意公有的属性直接是不可见的,必须为它们编写getter方法才行)
- 公有方法通过哈希表模型来取得,就像模板方法模型那样,
- 因此可以使用 <literal>model.doBar()</literal> 来调用
- <literal>object.doBar()</literal>。下面我们来更多讨论一下方法模型功能。</para>
-
- <para>如果请求的键值不能映射到一个bean的属性或方法时,
- 那么框架将试图定位到"通用的get方法",这个方法的签名是
- public <literal><replaceable>any-return-type</replaceable> get(String)</literal> 或
- public <literal><replaceable>any-return-type</replaceable> get(Object)</literal>,
- 使用请求键值来调用它们。请注意,这样就使得访问 <literal>java.util.Map</literal>
- 和其他类似类型的键值对非常便利。只要map的键是 <literal>String</literal> 类型的,
- 属性和方法名可以在映射中查到。(有一种解决方法可以用来避免在映射中遮挡名称,请继续来阅读。)
- 要而且注意 <literal>java.util.ResourceBundle</literal> 对象的方法使用
- <literal>getObject(String)</literal> 方法作为通用的get方法。</para>
-
- <para>如果在 <literal>BeansWrapper</literal> 实例中调用了
- <literal>setExposeFields(true)</literal> 方法,那么它仍然会暴露出类的公有的,
- 非静态的变量,用它们作为哈希表的键和值。即如果 <literal>foo</literal>
- 是类 <literal>Bar</literal> 的一个公有的,非静态的变量,而
- <literal>bar</literal> 是一个包装了 <literal>Bar</literal> 实例模板变量,
- 那么表达式 <literal>bar.foo</literal> 的值将会作为 <literal>bar</literal>
- 对象中 <literal>foo</literal> 变量的值。所有这个类的超类中公有变量都会被暴露出来。</para>
- </section>
-
- <section>
- <title>说一点安全性</title>
-
- <para>默认情况下,一些方法不能访问,它们被认为是模板中不安全的。
- 比如,不能使用同步方法(<literal>wait</literal>,<literal>notify</literal>,
- <literal>notifyAll</literal>),线程和线程组的管理方法(<literal>stop</literal>,
- <literal>suspend</literal>,<literal>resume</literal>,<literal>setDaemon</literal>,
- <literal>setPriority</literal>),反射相关方法(<literal>Field</literal>,
- <literal>set<replaceable>Xxx</replaceable></literal> 方法,
- <literal>Method.invoke</literal>,<literal>Constructor.newInstance</literal>,
- <literal>Class.newInstance</literal>,<literal>Class.getClassLoader</literal>等),
- <literal>System</literal> 和 <literal>Runtime</literal> 类中各种有危险性的方法
- (<literal>exec</literal>,<literal>exit</literal>,<literal>halt</literal>,
- <literal>load</literal>等)。<literal>BeansWrapper</literal> 也有一些安全级别
- (被称作"方法暴露的级别"),默认的级别被称作为 <literal>EXPOSE_SAFE</literal>,
- 它可能对大多数应用程序来说是适用的。没有安全保证的级别称作是
- <literal>EXPOSE_ALL</literal>,它允许你调用上述的不安全的方法。一个严格的级别是
- <literal>EXPOSE_PROPERTIES_ONLY</literal>,它只会暴露出bean属性的getters方法。
- 最后,一个称作是 <literal>EXPOSE_NOTHING</literal> 的级别,它不会暴露任何属性和方法。
- 这种情况下,你可以通过哈希表模型接口访问的那些数据只是map和资源包中的项,
- 还有,可以从通用 <literal>get(Object)</literal> 方法和 <literal>get(String)</literal>
- 方法调用返回的对象,所提供的受影响的对象就有这样的方法。</para>
- </section>
-
- <section>
- <title>模板标量模型功能(TemplateScalarModel functionality)</title>
-
- <para>对于 <literal>java.lang.String</literal> 对象的模型会实现
- <literal>TemplateScalarModel</literal> 接口,这个接口中的
- <literal>getAsString()</literal> 方法简单代替了 <literal>toString()</literal> 方法。
- 要注意把 <literal>String</literal> 对象包装到Bean包装器中,
- 要提供比它们作为标量时更多的功能:因为哈希表接口描述了上述所需功能,
- 那么包装 <literal>String</literal> 的模型也会提供访问所有
- <literal>String</literal> 的方法(<literal>indexOf</literal>,
- <literal>substring</literal> 等),尽管它们中很多都有内部的FreeMarker相同的实现,
- 最好使用它们(<literal>s?index_of(n)</literal>,<literal>s[start..<end]</literal> 等)。</para>
- </section>
-
- <section>
- <title>模板数字模型功能(TemplateNumberModel functionality)</title>
-
- <para>对于是 <literal>java.lang.Number</literal> 的实例对象的模型包装器,
- 它们实现了 <literal>TemplateNumberModel</literal> 接口,接口中的
- <literal>getAsNumber()</literal> 方法返回被包装的数字对象。
- 请注意把 <literal>Number</literal> 对象包装到Bean包装器中,
- 要提供比它们作为数字时更多的功能:因为哈希表接口描述了上述所需功能,
- 那么包装 <literal>Number</literal> 的模型也会提供访问所有他们的方法。</para>
- </section>
-
- <section>
- <title>模板集合模型功能(TemplateCollectionModel functionality)</title>
-
- <para>对于本地的Java数组和其他所有实现了 <literal>java.util.Collection</literal>
- 接口的类的模型包装器,都实现了 <literal>TemplateCollectionModel</literal> 接口,
- 因此也增强了使用 <literal>list</literal> 指令的附加功能。</para>
- </section>
-
- <section>
- <title>模板序列模型功能(TemplateSequenceModel functionality)</title>
-
- <para>对于本地的Java数组和其他所有实现了 <literal>java.util.List</literal>
- 接口的类的模型包装器,都实现了 <literal>TemplateSequenceModel</literal> 接口,
- 这样,它们之中的元素就可以使用 <literal>model[i]</literal> 这样的语法通过索引来访问了。
- 你也可以使用内建函数 <literal>model?size</literal> 来查询数组的长度和列表的大小。</para>
-
- <para>而且,所有的方法都可指定的一个单独的参数,从
- <literal>java.lang.Integer</literal>(即<literal>int</literal>, <literal>long</literal>,
- <literal>float</literal>, <literal>double</literal>,<literal>java.lang.Object</literal>,
- <literal>java.lang.Number</literal>,<literal>java.lang.Integer</literal>)
- 中通过反射方法调用,这些类也实现了这个接口。
- 这就意味着你可以通过很方便的方式来访问被索引的bean属性:
- <literal>model.foo[i]</literal> 将会翻译为 <literal>obj.getFoo(i)</literal>。</para>
- </section>
-
- <section xml:id="beanswrapper_method">
- <title>模板方法模型功能(TemplateMethodModel functionality)</title>
-
- <para>一个对象的所有方法作为 <literal>TemplateMethodModelEx</literal> 对象的表述,
- 它们在对象模型的方法名中使用哈希表的键来访问。当使用
- <literal>model.<replaceable>method</replaceable>(<replaceable>arg1</replaceable>,
- <replaceable>arg2</replaceable>,
- <replaceable>...</replaceable>)</literal> 来调用方法时,形参被作为模板模型传递给方法。
- 方法首先不会包装它们,后面我们会说到解包的详细内容。
- 这些不被包装的参数之后被实际方法来调用。以防止方法被重载,
- 许多特定的方法将会被选择使用相同的规则,也就是Java编译器从一些重载的方法中选择一个方法。
- 以防止没有方法签名匹配传递的参数,或者没有方法可以被无歧义地选择,
- 将会抛出 <literal>TemplateModelException</literal> 异常。</para>
-
- <para>返回值类型为 <literal>void</literal> 的方法返回
- <literal>TemplateModel.NOTHING</literal>,那么它们就可以使用
- <literal>${obj.method(args)}</literal> 形式的语法被安全地调用。</para>
-
- <para><literal>java.util.Map</literal> 实例的模型仍然实现了
- <literal>TemplateMethodModelEx</literal> 接口,作为调用它们
- <literal>get()</literal> 方法的一种方式。正如前面所讨论的那样,
- 你可以使用哈希表功能来访问"get"方法,但是它有一些缺点:
- 因为第一个属性和方法名会被键名来检查,所以执行过慢;
- 属性,方法名相冲突的键将会被隐藏;最终这种方法中你只可使用
- <literal>String</literal> 类型的键。对比一下,调用
- <literal>model(key)</literal> 方法,将直接翻译为
- <literal>model.get(key)</literal>:因为没有属性和方法名的查找,
- 速度会很快;不容易被隐藏;最终对非字符串的键也能正常处理,
- 因为参数没有被包装,只是被普通的方法调用。实际上,
- <literal>Map</literal> 中的 <literal>model(key)</literal> 和
- <literal>model.get(key)</literal> 是相等的,只是写起来很短罢了。</para>
-
- <para><literal>java.util.ResourceBundle</literal> 类的模型也实现了
- <literal>TemplateMethodModelEx</literal> 接口,
- 作为一种访问资源和信息格式化的方便形式。对资源包的单参数调用,
- 将会取回名称和未包装参数的 <literal>toString()</literal> 方法返回值一致的资源。
- 对资源包的多参数调用的情况和单参数一样,但是它会将参数作为格式化的模式传递给
- <literal>java.text.MessageFormat</literal>,在第二个和后面的作为格式化的参数中使用未包装的值。
- <literal>MessageFormat</literal> 对象将会使用它们原本的本地化资源包来初始化。</para>
- </section>
-
- <section>
- <title>解包规则</title>
-
- <para>当从模板中调用Java方法时,它的参数需要从模板模型转换回Java对象。
- 假设目标类型(方法常规参数被声明的类型)是用来 <literal>T</literal> 代表的,
- 下面的规则将会按下述的顺序进行依次尝试:</para>
-
- <itemizedlist>
- <listitem>
- <para>对包装器来说,如果模型是空模型,
- 就返回Java中的 <literal>null</literal>。</para>
- </listitem>
-
- <listitem>
- <para>如果模型实现了 <literal>AdapterTemplateModel</literal> 接口,
- 如果它是 <literal>T</literal> 的实例,
- 或者它是一个数字而且可以使用下面第三点描述的数字强制转换成 <literal>T</literal>,
- 那么 <literal>model.getAdaptedObject(T)</literal> 的结果会返回。
- <phrase role="forProgrammers">由BeansWrapper创建的所有方法是AdapterTemplateModel的实现,
- 所以由BeansWrapper为基本的Java对象创建的展开模型通常不如初始的Java对象。</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型实现了已经废弃的 <literal>WrapperTemplateModel</literal> 接口,
- 如果它是 <literal>T</literal> 的实例,
- 或者它是一个数字而且可以使用下面第二点描述的数字强制转换成
- <literal>T</literal> ,那么 <literal>model.getWrappedObject()</literal>
- 方法的结果会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是 <literal>java.lang.String</literal> 类型,
- 那么如果模型实现了 <literal>TemplateScalarModel</literal> 接口,它的字符串值将会返回。
- <phrase role="forProgrammers">请注意,如果模型没有实现接口,
- 我们不能尝试使用String.valueOf(model)方法自动转换模型到String类型。
- 这里不得不使用内建函数?string明确地用字符串来处理非标量。</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是原始的数字类型或者是可由
- <literal>T</literal> 指定的 <literal>java.lang.Number</literal>
- 类型,还有模型实现了 <literal>TemplateNumberModel</literal> 接口,
- 如果它是 <literal>T</literal> 的实例或者是它的装箱类型
- (如果 <literal>T</literal> 是原始类型),那么它的数字值会返回。
- 否则,如果 <literal>T</literal> 是一个Java内建的数字类型
- (原始类型或是 <literal>java.lang.Number</literal> 的标准子类,
- 包括 <literal>BigInteger</literal> 和 <literal>BigDecimal</literal>),
- 类型 <literal>T</literal>
- 的一个新对象或是它的装箱类型会由数字模型的适当强制的值来生成。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是 <literal>boolean</literal> 值或
- <literal>java.lang.Boolean</literal> 类型,模型实现了
- <literal>TemplateBooleanModel</literal> 接口,那么布尔值将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是
- <literal>java.util.Map</literal> 类型,模型实现了
- <literal>TemplateHashModel</literal> 接口,
- 那么一个哈希表模型的特殊Map表示对象将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是
- <literal>java.util.List</literal> 类型,模型实现了
- <literal>TemplateSequenceModel</literal> 接口,
- 那么一个序列模型的特殊List表示对象将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是
- <literal>java.util.Set</literal> 类型,模型实现了
- <literal>TemplateCollectionModel</literal> 接口,
- 那么集合模型的一个特殊Set表示对象将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是
- <literal>java.util.Collection</literal> 或
- <literal>java.lang.Iterable</literal> 类型,模型实现了
- <literal>TemplateCollectionModel</literal> 或
- <literal>TemplateSequenceModel</literal> 接口,
- 那么集合或序列模型(各自地)一个特殊的Set或List表示对象将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是Java数组类型,模型实现了
- <literal>TemplateSequenceModel</literal> 接口,
- 那么一个新的指定类型的数组将会创建,
- 它其中的元素使用数组的组件类型作为 <literal>T</literal>,
- 递归展开到数组中。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 是 <literal>char</literal> 或
- <literal>java.lang.Character</literal> 类型,模型实现了
- <literal>TemplateScalarModel</literal> 接口,
- 它的字符串表示中包含精确的一个字符,那么一个
- <literal>java.lang.Character</literal> 类型的值将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>T</literal> 定义的是
- <literal>java.util.Date</literal> 类型,模型实现了
- <literal>TemplateDateModel</literal> 接口,
- 而且它的日期值是 <literal>T</literal> 的实例,
- 那么这个日期值将会返回。</para>
- </listitem>
-
- <listitem>
- <para>如果模型是数字模型,而且它的数字值是 <literal>T</literal>
- 的实例,那么数字值就会返回。<phrase role="forProgrammers">
- 你可以得到一个实现了自定义接口的
- java.lang.Number类型的自定义子类,也许T就是那个接口。(*)</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是日期类型,而且它的日期值是 <literal>T</literal> 的实例,
- 那么日期值将会返回。<phrase role="forProgrammers">类似的考虑为(*)</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是标量类型,而且 <literal>T</literal>
- 可以从 <literal>java.lang.String</literal> 类型来定义,
- 那么字符串值将会返回。<phrase role="forProgrammers">
- 这种情况涵盖T是java.lang.Object,
- java.lang.Comparable和java.io.Serializable类型。(**)</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是布尔类型,而且 <literal>T</literal>
- 可以从 <literal>java.lang.Boolean</literal> 类型来定义,
- 那么布尔值将会返回。
- <phrase role="forProgrammers">和(**)是相同的</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是哈希表类型,而且 <literal>T</literal>
- 可以从 <literal>freemarker.ext.beans.HashAdapter</literal> 类型来定义,
- 那么一个哈希表适配器将会返回。
- <phrase role="forProgrammers">和(**)是相同的</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是序列类型,而且 <literal>T</literal>
- 可以从 <literal>freemarker.ext.beans.SequenceAdapter</literal> 类型来定义,
- 那么一个序列适配器将会返回。
- <phrase role="forProgrammers">和(**)是相同的</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是集合类型,而且 <literal>T</literal>
- 可以从 <literal>freemarker.ext.beans.SetAdapter</literal> 类型来定义,
- 那么集合的set适配器将会返回。
- <phrase role="forProgrammers">和(**)是相同的</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果模型是 <literal>T</literal> 的实例,那么模型本身将会返回。
- <phrase role="forProgrammers">这种情况涵盖方法明确地声明一个 FreeMarker 特定模型接口,
- 而且允许返回指令,当java.lang.Object被请求时允许返回方法和转换的模型。</phrase></para>
- </listitem>
-
- <listitem>
- <para>意味着没有可能转换的异常被抛出。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>访问静态方法</title>
-
- <indexterm>
- <primary>static method</primary>
-
- <secondary>accessing from templates</secondary>
- </indexterm>
-
- <para>从 <literal>BeansWrapper.getStaticModels()</literal> 方法返回的
- <literal>TemplateHashModel</literal>
- 可以用来创建哈希表模型来访问任意类的静态方法和字段。</para>
-
- <programlisting role="unspecified">BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
-TemplateHashModel staticModels = wrapper.getStaticModels();
-TemplateHashModel fileStatics =
- (TemplateHashModel) staticModels.get("java.io.File");</programlisting>
-
- <para>之后就可以得到模板的哈希表模型,它会暴露所有
- <literal>java.lang.System</literal> 类的静态方法和静态字段
- (final类型和非final类型)作为哈希表的键。
- 设想你已经将之前的模型放到根root模型中了:</para>
-
- <programlisting role="unspecified">root.put("File", fileStatics);</programlisting>
-
- <para>从现在开始,你可以在模板中使用 <literal>${File.SEPARATOR}</literal>
- 来插入文件分隔符,或者你可以列出所有文件系统中的根元素,通过:</para>
-
- <programlisting role="template"><#list File.listRoots() as fileSystemRoot>...</#list></programlisting>
-
- <para>当然,你必须小心这个模型所带来的潜在的安全问题。</para>
-
- <para>你可以给模板作者完全的自由,
- 不管它们通过将静态方法的哈希表放到模板的根模型中,
- 来使用哪种类的静态方法,如用如下方式:</para>
-
- <programlisting role="unspecified">root.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());</programlisting>
-
- <para>如果它被用作是以类名为键的哈希表,
- 这个对象暴露的只是任意类的静态方法。那么你可以在模板中使用如
- <literal>${statics["java.lang.System"].currentTimeMillis()}</literal>
- 这样的表达式。请注意,这样会有更多的安全隐患,比如,
- 如果方法暴露级别对 <literal>EXPOSE_ALL</literal> 是很弱的,
- 那么某些人可以使用这个模型调用 <literal>System.exit()</literal> 方法。</para>
-
- <para>请注意,在上述的示例中,我们通常使用默认的
- <literal>BeansWrapper</literal> 实例。这是一个方便使用的静态包装器实例,
- 你可以在很多情况下使用。特别是你想修改一些属性
- (比如模型缓存,安全级别,或者是空模型对象表示)时,
- 你也可以自由地来创建自己的 <literal>BeansWrapper</literal> 实例,
- 然后用它们来代替默认包装器。</para>
- </section>
-
- <section xml:id="jdk_15_enums">
- <title>访问枚举类型</title>
-
- <indexterm>
- <primary>enum</primary>
- </indexterm>
-
- <para>在JRE 1.5版本之后,从方法 <literal>BeansWrapper.getEnumModels()</literal>
- 返回的 <literal>TemplateHashModel</literal> 可以被用作创建访问枚举类型值的哈希表模型。
- (试图在之前JRE中调用这个方法会导致 <literal>UnsupportedOperationException</literal> 异常。)</para>
-
- <programlisting role="unspecified">BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
-TemplateHashModel enumModels = wrapper.getEnumModels();
-TemplateHashModel roundingModeEnums =
- (TemplateHashModel) enumModels.get("java.math.RoundingMode");</programlisting>
-
- <para>这样你就可以得到模板哈希表模型,它暴露了
- <literal>java.math.RoundingMode</literal> 类所有枚举类型的值,
- 并把它们作为哈希表的键。设想你将之前的模型已经放入root模型中了:</para>
-
- <programlisting role="unspecified">root.put("RoundingMode", roundingModeEnums);</programlisting>
-
- <para>现在开始,你可以在模板中使用表达式
- <literal>RoundingMode.UP</literal> 来引用枚举值 <literal>UP</literal>。</para>
-
- <para>你可以给模板作者完全的自由,不管它们使用哪种枚举类,
- 将枚举模型的哈希表放到模板的root模型中,可以这样来做:</para>
-
- <programlisting role="unspecified">root.put("enums", BeansWrapper.getDefaultInstance().getEnumModels());</programlisting>
-
- <para>如果它被用作是类名作为键的哈希表,这个对象暴露了任意的枚举类。
- 那么可以在模板中使用如
- <literal>${enums["java.math.RoundingMode"].UP}</literal> 的表达式。</para>
-
- <para>被暴露的枚举值可以被用作是标量(它们会委派它们的
- <literal>toString()</literal> 方法),也可以用在相同或不同的比较中。</para>
-
- <para>请注意,在上述的例子中,我们通常使用默认的
- <literal>BeansWrapper</literal> 实例。这是一个方便使用的静态包装器实例,
- 你可以在很多情况下使用。特别是你想修改一些属性
- (比如模型缓存,安全级别,或者是空模型对象表示)时,
- 你也可以自由地来创建自己的 <literal>BeansWrapper</literal> 实例,
- 然后用它们来代替默认包装器。</para>
- </section>
- </section>
-
- <section xml:id="pgui_misc_logging">
- <title>日志</title>
-
- <indexterm>
- <primary>logging</primary>
- </indexterm>
-
- <indexterm>
- <primary>SLF4J</primary>
- </indexterm>
-
- <indexterm>
- <primary>Log4j2</primary>
- </indexterm>
-
- <section>
- <title>日志库选择</title>
-
- <para>简而言之,在现代(比如2015年)的应用程序中,
- 记录日志推荐使用SLF4J API。
- 要让 FreeMarker 2.3.x. 使用SLF4J,在项目中加入依赖
- <literal>org.slf4j:log4j-over-slf4j</literal> 即可,
- 要确保 <literal>log4j:log4j</literal>
- 不能存在。(从 FreeMarker 2.4.x 开始,尽管没有什么害处,
- 但也不再需要 <literal>log4j-over-slf4j</literal> 了。)</para>
-
- <para>如果你对这些细节好奇,或者不能使用SLF4J,那么就继续阅读吧...</para>
-
- <para>FreeMarker 整合了如下的日志包:<link
- xlink:href="http://www.slf4j.org/">SLF4J</link>,<link
- xlink:href="http://commons.apache.org/logging/">Apache Commons Logging</link>,<link
- xlink:href="http://jakarta.apache.org/log4j">Log4J</link> 1.x,<link
- xlink:href="http://jakarta.apache.org/avalon/logkit">Avalon LogKit</link> 和 <link
- xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/util/logging/package-summary.html">
- <literal>java.util.logging</literal></link>。默认情况下,
- FreeMarker(在2.3.x版本下)会按如下顺序来查找日志包,
- 而且会自动使用第一个发现的包:<remark>[2.4 - un-remark this] SLF4J, Apache Commons Logging, </remark>
- LOG4J(从2.3.22开始,如果正确安装了<literal>log4j-over-slf4j</literal>,则会使用SLF4J来代替),
- Apache Avalon LogKit, <literal>java.util.logging</literal>。
- 正如你所见,Log4j有最高的优先级。<literal>org.apache.log4j.Logger</literal>
- 类会检测Log4j的存在,那么也就是说,像<literal>log4j-over-slf4j</literal> 或
- <literal>log4j-1.2-api</literal>,Log4j重定向也会有最高优先级。</para>
-
- <para>在 FreeMarker 2.4 版本之前,因为向后兼容性的限制,
- SLF4J和Apache Commons Logging不会被自动搜索。但是如果你正确安装了
- <literal>org.slf4j:log4j-over-slf4j</literal>(也就意味着,
- 在类路径下没有真实的Log4j,SLF4J有一个像 <literal>logback-classic</literal>
- 的支持实现),那么FreeMarker会直接使用SLF4J API来代替Log4j API
- (从FreeMarker 2.3.22版本开始)。</para>
-
- <para>请注意,应用Log4j2日志有个相似的技巧:如果
- <literal>org.apache.logging.log4j:log4j-1.2-api</literal> 可用,
- FreeMarker 2.3.x会使用它,因为它看起来就像Log4j,
- 但是所有的消息都会自动到Log4j2中。</para>
-
- <para>如果自动检测没有给出你想要的结果,那么你可以设置系统属性
- <literal>org.freemarker.loggerLibrary</literal> 来明确选择
- (从2.3.22版本开始)一个日志库,比如:</para>
-
- <programlisting role="unspecified">java <replaceable>...</replaceable> -Dorg.freemarker.loggerLibrary=SLF4J</programlisting>
-
- <para>系统属性支持的值有:
- <literal>SLF4J</literal>, <literal>CommonsLogging</literal>,
- <literal>JUL</literal> (即 <literal>java.util.logging</literal>),
- <literal>Avalon</literal>, <literal>auto</literal> (默认行为),
- <literal>none</literal> (关闭日志)。</para>
-
- <para>请注意,为了可靠的运行,系统属性应该在JVM启动时(向上面那样)就该设置好,
- 而不是在Java代码之后。</para>
-
- <para>推荐使用SLF4J,因为它在 FreeMarker 中运行的更好,
- 也是因为从 FreeMarker 2.4 版本开始它有自动检测的最高优先级。</para>
- </section>
-
- <section>
- <title>日志分类</title>
-
- <para>由FreeMarker产生的所有日志信息会被记录到名称由
- <literal>freemarker.</literal>开头的日志记录器中。
- 现在被使用的记录器是:</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>日志分类名称</th>
-
- <th>目标</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>freemarker.beans</literal></td>
-
- <td>记录Beans包装器模块的日志信息。</td>
- </tr>
-
- <tr>
- <td><literal>freemarker.cache</literal></td>
-
- <td>记录模板加载和缓存相关的日志信息。</td>
- </tr>
-
- <tr>
- <td><literal>freemarker.runtime</literal></td>
-
- <td>记录在模板执行期间的和特定分类无关的相关信息。
- 更重要的是,它会记录模板异常并在模板处理期间抛出
- (但它却应该在现行的应用程序中禁用;稍后将会解释)。</td>
- </tr>
-
- <tr>
- <td><literal>freemarker.runtime.attempt</literal></td>
-
- <td>记录在模板执行期间抛出的模板异常日志信息,
- 但是是开启DEBUG严重级别,并由
- <literal>attempt</literal>/<literal>recover</literal> 指令捕捉。
- 请注意,该异常也会被记录到正常的日志记录器中
- (比如<literal>freemarker.runtime</literal>)。</td>
- </tr>
-
- <tr>
- <td><literal>freemarker.servlet</literal></td>
-
- <td>记录来自 <literal>FreemarkerServlet</literal> 类的消息。</td>
- </tr>
-
- <tr>
- <td><literal>freemarker.jsp</literal></td>
-
- <td>记录FreeMarker JSP 支持的消息。</td>
- </tr>
- </tbody>
- </informaltable>
-
- <para>FreeMarker 会在模板执行期间使用
- <literal>freemarker.runtime</literal> 记录异常,即便异常继续增加,最终由
- <literal>Template.process</literal> 或 <literal>Environment.process</literal> 抛出。
- (那些都是从应用程序或框架中调用模板时的API调用。)
- 良好的应用程序会记录它们抛出的异常,极少数情况下是处理它们而不去记录日志。
- 但是FreeMarker已经记录了异常,那么就会得到比期望的多一条日志记录。
- 要修复这个问题(从2.3.22版本开始),可以设置 <literal>log_template_exceptions</literal>
- (<literal>Configurable.setLogTemplateExceptions(boolean)</literal>)
- 为 <literal>false</literal>。</para>
- </section>
- </section>
-
- <section xml:id="pgui_misc_servlet">
- <title>在Servlet中使用FreeMarker</title>
-
- <anchor xml:id="topic.servlet"/>
-
- <indexterm>
- <primary>Servlet</primary>
- </indexterm>
-
- <indexterm>
- <primary>HTTP</primary>
- </indexterm>
-
- <indexterm>
- <primary>JSP</primary>
- </indexterm>
-
- <indexterm>
- <primary>Model 2</primary>
- </indexterm>
-
- <indexterm>
- <primary>Struts</primary>
- </indexterm>
-
- <indexterm>
- <primary>Web应用框架</primary>
- </indexterm>
-
- <para>作为基础了解,在web应用程序范畴内使用 FreeMarker 和其它并没有什么不同;
- FreeMarker将它的输出写入传递给 <literal>Template.process</literal> 方法的
- <literal>Writer</literal> 对象,它不关心 <literal>Writer</literal>
- 将输出写入控制台,文件或是 <literal>HttpServletResponse</literal> 的输出流。
- FreeMarker 并不知道什么是servlet和web;它仅仅是使用模板文件来合并Java对象,
- 之后从它们中间生成输出文本。从这里可知,如何创建一个Web应用程序都随你的习惯来。</para>
-
- <para>但是,你可能想在已经存在的Web应用框架中使用FreeMarker。
- 许多框架都是基于"Model 2"架构的,JSP页面来控制显示。
- 如果你使用了这样的框架(比如<link
- xlink:href="http://jakarta.apache.org/struts">Apache Struts</link>),
- 那么可以继续阅读本文。对于其他框架请参考它们的文档。</para>
-
- <section xml:id="pgui_misc_servlet_model2">
- <title>在"Model 2"中使用FreeMarker</title>
-
- <para>许多框架依照HTTP请求转发给用户自定义的"action"类,
- 将数据作为属性放在 <literal>ServletContext</literal>,
- <literal>HttpSession</literal> 和 <literal>HttpServletRequest</literal> 对象中,
- 之后请求被框架派发到一个JSP页面中(视图层),使用属性传递过来的数据来生成HTML页面,
- 这样的策略通常就是所指的Model 2模型。</para>
-
- <mediaobject>
- <imageobject>
- <imagedata fileref="figures/model2sketch.png"/>
- </imageobject>
- </mediaobject>
-
- <para>使用这样的框架,你就可以非常容易地用FTL文件来代替JSP文件。
- 但是,因为你的Servlet容器(Web应用程序服务器),不像JSP文件,
- 它可能并不知道如何处理FTL文件,那么就需要对Web应用程序进行一些额外的配置:</para>
-
- <orderedlist>
- <listitem>
- <para>复制 <literal>freemarker.jar</literal>
- (从FreeMarker发布包的<literal>lib</literal>目录中)
- 到Web应用程序的 <literal>WEB-INF/lib</literal> 目录下。</para>
- </listitem>
-
- <listitem>
- <para>将下面的部分添加到Web应用程序的
- <literal>WEB-INF/web.xml</literal> 文件中
- (调整部分内容是否需要):</para>
- </listitem>
- </orderedlist>
-
- <programlisting role="unspecified"><servlet>
- <servlet-name>freemarker</servlet-name>
- <servlet-class><emphasis>freemarker.ext.servlet.FreemarkerServlet</emphasis></servlet-class>
-
- <!-- FreemarkerServlet settings: -->
- <init-param>
- <param-name>TemplatePath</param-name>
- <param-value>/</param-value>
- </init-param>
- <init-param>
- <param-name>NoCache</param-name>
- <param-value>true</param-value>
- </init-param>
- <init-param>
- <param-name>ContentType</param-name>
- <param-value>text/html; charset=UTF-8</param-value> <!-- Forces UTF-8 output encoding! -->
- </init-param>
-
- <!-- FreeMarker settings: -->
- <init-param>
- <param-name>incompatible_improvements</param-name>
- <param-value>2.3.22</param-value>
- <!-- Recommended to set to a high value. For the details, see the Java API docs of
- freemarker.template.Configuration#Configuration(Version). -->
- </init-param>
- <init-param>
- <param-name>template_exception_handler</param-name>
- <!-- Use "html_debug" instead during development! -->
- <param-value>rethrow</param-value>
- </init-param>
- <init-param>
- <param-name>template_update_delay</param-name>
- <!-- ATTENTION, 0 is for development only! Use higher value otherwise. -->
- <param-value>0</param-value>
- </init-param>
- <init-param>
- <param-name>default_encoding</param-name>
- <!-- The encoding of the template files. -->
- <param-value>UTF-8</param-value>
- </init-param>
- <init-param>
- <param-name>locale</param-name>
- <!-- Influences number and date/time formatting, etc. -->
- <param-value>en_US</param-value>
- </init-param>
- <init-param>
- <param-name>number_format</param-name>
- <param-value>0.##########</param-value>
- </init-param>
-
- <load-on-startup>1</load-on-startup>
-</servlet>
-
-<servlet-mapping>
- <servlet-name>freemarker</servlet-name>
- <url-pattern><emphasis>*.ftl</emphasis></url-pattern>
-</servlet-mapping>
-
-<replaceable>...</replaceable>
-
-<!--
- Prevent the visiting of MVC Views from outside the servlet container.
- RequestDispatcher.forward/include should, and will still work.
- Removing this may open security holes!
--->
-<security-constraint>
- <web-resource-collection>
- <web-resource-name>FreeMarker MVC Views</web-resource-name>
- <url-pattern>*.ftl</url-pattern>
- </web-resource-collection>
- <auth-constraint>
- <!-- Nobody is allowed to visit these directly. -->
- </auth-constraint>
-</security-constraint></programlisting>
-
- <para>在这之后,你可以像使用JSP(<literal>*.jsp</literal>)
- 文件那样使用FTL文件(<literal>*.ftl</literal>)了。
- (当然你可以选择除 <literal>ftl</literal> 之外的扩展名;这只是惯例)</para>
-
- <note>
- <para>它是怎么工作的?让我们先来看看JSP是怎么工作的。
- 许多servlet容器处理JSP时使用一个映射为 <literal>*.jsp</literal>
- 的servlet请求URL格式。这样servlet就会接收所有URL是以
- <literal>.jsp</literal> 结尾的请求,查找请求URL地址中的JSP文件,
- 内部编译后生成 <literal>Servlet</literal>,然后调用生成好的serlvet来生成页面。
- 这里为URL类型是 <literal>*.ftl</literal> 映射的
- <literal>FreemarkerServlet</literal> 也是相同功能,只是FTL文件不会编译成
- <literal>Servlet</literal>,而是给 <literal>Template</literal> 对象,
- 之后 <literal>Template</literal> 对象的 <literal>process</literal>
- 方法就会被调用来生成页面。</para>
- </note>
-
- <para><anchor xml:id="topic.servlet.scopeAttr"/>比如,代替这个JSP页面
- (注意它使用了Struts标签库来保存设计,而不是嵌入可怕的Java代码):</para>
-
- <programlisting role="template"><%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
-<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
-
-<html>
-<head><title>Acmee Products International</title>
-<body>
- <h1>Hello <bean:write name="user"/>!</h1>
- <p>These are our latest offers:
- <ul>
- <logic:iterate name="latestProducts" id="prod">
- <li><bean:write name="prod" property="name"/>
- for <bean:write name="prod" property="price"/> Credits.
- </logic:iterate>
- </ul>
-</body>
-</html></programlisting>
-
- <para>你可以使用这个FTL文件(使用 <literal>ftl</literal>
- 扩展名而不是 <literal>jsp</literal>):</para>
-
- <programlisting role="template"><html>
-<head><title>Acmee Products International</title>
-<body>
- <h1>Hello ${user}!</h1>
- <p>These are our latest offers:
- <ul>
- <#list latestProducts as prod>
- <li>${prod.name} for ${prod.price} Credits.
- </#list>
- </ul>
-</body>
-</html></programlisting>
-
- <warning>
- <para>在 FreeMarker 中,<literal><html:form
- action="/query"><replaceable>...</replaceable></html:form></literal>
- 仅仅被视为是静态文本,所以它会按照原本输出出来了,就像其他XML或HTML标记一样。
- JSP标签也仅仅是FreeMarker的指令,没有什么特殊之处,所以你可以
- <emphasis>使用FreeMarker语法</emphasis> 形式来调用它们,而不是JSP语法:
- <literal><@html.form
- action="/query"><replaceable>...</replaceable></@html.form></literal>。
- 注意在FreeMarker语法中 <emphasis>不能像JSP那样在参数中使用
- <literal>${<replaceable>...</replaceable>}</literal></emphasis>,
- 而且<emphasis>不能给参数值加引号</emphasis>。
- 所以这样是<emphasis>错误的</emphasis>:</para>
-
- <programlisting role="template"><#-- WRONG: -->
-<@my.jspTag color="${aVariable}" name="aStringLiteral"
- width="100" height=${a+b} /></programlisting>
-
- <para>但下面这样是正确的:</para>
-
- <programlisting role="template"><#-- Good: -->
-<@my.jspTag color=aVariable name="aStringLiteral"
- width=100 height=a+b /></programlisting>
- </warning>
-
- <para>在这两个模板中,当你要引用 <literal>user</literal> 和
- <literal>latestProduct</literal> 时,首先它会尝试去查找已经在模板中创建的同名变量
- (比如 <literal>prod</literal>;如果你使用JSP:这是一个page范围内的属性)。
- 如果那样做不行,它会尝试在 <literal>HttpServletRequest</literal> 对象中查找那个名字的属性,
- 如果没有找到就在 <literal>HttpSession</literal> 中找,如果还没有找到那就在
- <literal>ServletContext</literal> 中找。FTL按这种情况工作是因为
- <literal>FreemarkerServlet</literal> 创建数据模型由上面提到的3个对象中的属性而来。
- 那也就是说,这种情况下根哈希表root不是 <literal>java.util.Map</literal>
- (正如本手册中的一些例子那样),而是
- <literal>ServletContext</literal>+<literal>HttpSession</literal>+<literal>HttpServletRequest</literal>
- ;FreeMarker 在处理数据模型类型的时候非常灵活。所以如果你想将变量
- <literal>"name"</literal> 放到数据模型中,那么你可以调用
- <literal>servletRequest.setAttribute("name", "Fred")</literal>;这是模型2的逻辑,
- 而 FreeMarker 将会适应它。</para>
-
- <para><literal>FreemarkerServlet</literal> 也会在数据模型中放置3个哈希表,
- 这样你就可以直接访问3个对象中的属性了。这些哈希表变量是:<literal>Request</literal>,
- <literal>Session</literal>,<literal>Application</literal>
- (和<literal>ServletContext</literal>对应)。它还会暴露另外一个名为
- <literal>RequestParameters</literal> 的哈希表,这个哈希表提供访问HTTP请求中的参数。</para>
-
- <para><literal>FreemarkerServlet</literal> 也有很多初始参数。
- 它可以被设置从任意路径来加载模板,从类路径下,或相对于Web应用程序的目录。
- 你可以设置模板使用的字符集。你还可以设置想使用的对象包装器等等。</para>
-
- <para>通过子类别,<literal>FreemarkerServlet</literal> 易于定制特殊需要。
- 那就是说,如果你需要对所有模板添加一个额外的可用变量,使用servlet的子类,
- 覆盖 <literal>preTemplateProcess()</literal> 方法,在模板被执行前,
- 将你需要的额外数据放到模型中。或者在servlet的子类中,在
- <literal>Configuration</literal> 中设置这些全局的变量作为
- <link linkend="pgui_config_sharedvariables">共享变量</link>。</para>
-
- <para>要获取更多信息,可以阅读该类的Java API文档。</para>
- </section>
-
- <section xml:id="pgui_misc_servlet_include">
- <title>包含其它Web应用程序资源中的内容</title>
-
- <indexterm>
- <primary>include</primary>
-
- <secondary>servlet</secondary>
- </indexterm>
-
- <indexterm>
- <primary>include</primary>
-
- <secondary>JSP</secondary>
- </indexterm>
-
- <indexterm>
- <primary>servlet</primary>
-
- <secondary>include</secondary>
- </indexterm>
-
- <indexterm>
- <primary>JSP</primary>
-
- <secondary>include</secondary>
- </indexterm>
-
- <para>你可以使用由 <literal>FreemarkerServlet</literal> (2.3.15版本之后)
- 提供的客户化标签<literal><@include_page path="..."/></literal>
- 来包含另一个Web应用资源的内容到输出内容中;这对于整合JSP页面
- (在同一Web服务器中生活在FreeMarker模板旁边)
- 的输出到FreeMarker模板的输出中非常有用。使用:</para>
-
- <programlisting role="template"><@include_page path="path/to/some.jsp"/></programlisting>
-
- <para>和在JSP中使用该标签是相同的:</para>
-
- <programlisting role="template"><jsp:include page="path/to/some.jsp"></programlisting>
-
- <note>
- <para><literal><@include_page ...></literal> 不能和
- <literal><#include ...></literal>搞混,
- 后者是为了包含FreeMarker模板而不会牵涉到Servlet容器。
- 使用 <literal><#include ...></literal> 包含的模板和包含它的模板共享模板处理状态,
- 比如数据模型和模板语言变量,而 <literal><@include_page ...></literal>
- 开始一个独立的HTTP请求处理。</para>
- </note>
-
- <note>
- <para>一些Web应用框架为此提供它们自己的解决方案,
- 这种情况下你就可以使用它们来替代。
- 而一些Web应用框架不使用 <literal>FreemarkerServlet</literal>,
- 所以 <literal>include_page</literal> 是不可用的。</para>
- </note>
-
- <para>路径可以是相对的,也可以是绝对的。相对路径被解释成相对于当前HTTP请求
- (一个可以触发模板执行的请求)的URL,而绝对路径在当前的servlet上下文
- (当前的Web应用)中是绝对的。你不能从当前Web应用的外部包含页面。
- 注意你可以包含任意页面,而不仅仅是JSP页面;
- 我们仅仅使用以 <literal>.jsp</literal> 结尾的页面作为说明。</para>
-
- <para>除了参数 <literal>path</literal> 之外,你也可以用布尔值
- (当不指定时默认是true)指定一个名为 <literal>inherit_params</literal>
- 可选的参数来指定被包含的页面对当前的请求是否可见HTTP请求中的参数。</para>
-
- <para>最后,你可以指定一个名为 <literal>params</literal> 的可选参数,
- 来指定被包含页面可见的新请求参数。如果也传递继承的参数,
- 那么指定参数的值将会得到前缀名称相同的继承参数的值。<literal>params</literal>
- 的值必须是一个哈希表类型,它其中的每个值可以是字符串,
- 或者是字符串序列(如果你需要多值参数)。这里给出一个完整的示例:</para>
-
- <programlisting role="template"><@include_page path="path/to/some.jsp" inherit_params=true params={"foo": "99", "bar": ["a", "b"]}/></programlisting>
-
- <para>这会包含 <literal>path/to/some.jsp</literal> 页面,
- 传递它的所有的当前请求的参数,除了"foo"和"bar",
- 这两个会被分别设置为"99"和多值序列"a","b"。
- 如果原来请求中已经有这些参数的值了,那么新值会添加到原来存在的值中。
- 那就是说,如果"foo"有值"111"和"123",那么现在它会有"99","111","123"。</para>
-
- <para><phrase role="forProgrammers">事实上使用 <literal>params</literal>
- 给参数传递非字符串值是可能的。这样的一个值首先会被转换为适合的Java对象
- (数字,布尔值,日期等),之后调用它们Java对象的 <literal>toString()</literal>
- 方法来得到字符串值。最好不要依赖这种机制,作为替代,
- 明确参数值在模板级别不能转换成字符串类型之后,
- 在使用到它的地方可以使用内建函数 <literal>?string</literal>
- 和 <literal>?c</literal>。</phrase></para>
- </section>
-
- <section>
- <title>在FTL中使用自定义JSP标签</title>
-
- <indexterm>
- <primary>JSP</primary>
-
- <secondary>taglib</secondary>
- </indexterm>
-
- <indexterm>
- <primary>custom tags</primary>
- </indexterm>
-
- <indexterm>
- <primary>taglib</primary>
- </indexterm>
-
- <para><literal>FreemarkerServlet</literal> 将一个哈希表类型的
- <literal>JspTaglibs</literal> 放到数据模型中,就可以使用它来访问JSP标签库了。
- 自定义JSP标签库将被视为普通用户自定义指令来访问,自定义EL函数
- (从 FreeMarker 2.3.22 版本开始)视为方法。例如,这个JSP文件:</para>
-
- <programlisting role="template"><%@ page contentType="text/html;charset=ISO-8859-2" language="java"%>
-<%@ taglib prefix="e" uri="/WEB-INF/example.tld" %>
-<%@ taglib prefix="oe" uri="/WEB-INF/other-example.tld" %>
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-
-<%-- Custom JSP tags and functions: --%>
-
-<e:someTag numParam="123" boolParam="true" strParam="Example" anotherParam="${someVar}">
- ...
-</e:someTag>
-
-<oe:otherTag />
-
-${e:someELFunction(1, 2)}
-
-
-<%-- JSTL: --%>
-
-<c:if test="${foo}">
- Do this
-</c:if>
-
-<c:choose>
- <c:when test="${x == 1}">
- Do this
- </c:when>
- <c:otherwise>
- Do that
- </c:otherwise>
-</c:choose>
-
-<c:forEach var="person" items="${persons}">
- ${person.name}
-</c:forEach>
-
-${fn:trim(bar)}</programlisting>
-
- <para>基本一致的FTL是:</para>
-
- <programlisting role="template"><#assign e=JspTaglibs["/WEB-INF/example.tld"]>
-<#assign oe=JspTaglibs["/WEB-INF/other-example.tld"]>
-
-<#-- Custom JSP tags and functions: --#>
-
-<@e.someTag numParam=123 boolParam=true strParam="Example" anotherParam=someVar>
- ...
-</@e.someTag>
-
-<@oe.otherTag />
-
-${e.someELFunction(1, 2)}
-
-
-<#-- JSTL - Instead, use native FTL constructs: -->
-
-<#if foo>
- Do this
-</#if>
-
-<#if x == 1>
- Do this
-<#else>
- Do that
-</#if>
-
-<#list persons as person>
- ${person.name}
-</#list>
-
-${bar?trim}</programlisting>
-
- <note>
- <para>参数值没有使用引号,而且
- <literal>"${<replaceable>...</replaceable>}"</literal> 和JSP中使用的一样。
- 后面会详细解释。</para>
- </note>
-
- <note>
- <para><literal>JspTaglibs</literal> 不是 FreeMarker 的核心特性;
- 它只存在于通过 <literal>FreemarkerServlet</literal> 调用的模板。
- 这是因为JSP 标签/函数 假定一个servlet环境(FreeMarker不会),
- 加上一些Servlet概念被模仿成 <literal>FreemarkerServlet</literal>
- 创建的特定Freemarker数据模型。很多现代开发框架以纯净的方式使用FreeMarker,
- 而不是通过 <literal>FreemarkerServlet</literal>。</para>
- </note>
-
- <para>因为自定义JSP标签是在JSP环境中来书写操作的,它们假设变量
- (在JSP中常被指代"beans")被存储在4个范围中:page范围,request范围,
- session范围和application范围。FTL没有这样的表示法(4种范围),但是
- <literal>FreemarkerServlet</literal>给自定义标签提供仿真的环境,
- 这样就可以维持JSP范围中的"beans"和FTL变量之间的对应关系。
- 对于自定义的JSP标签,请求request,会话session和应用application是和真实JSP相同的:
- <literal>javax.servlet.ServletContext</literal>,<literal>HttpSession</literal>
- 和 <literal>ServletRequest</literal> 对象中的属性。从FTL的角度来看,
- 这三种范围都在数据模型中,这点前面已经解释了。page范围和FTL全局变量(参见<link
- linkend="ref.directive.global"><literal>global</literal>指令</link>)是对应的。
- 那也就是,如果你使用 <literal>global</literal> 指令创建一个变量,通过仿真的JSP环境,
- 它会作为page范围变量对自定义标签可见。而且,如果一个JSP标签创建了一个新的page范围变量,
- 那么结果和用 <literal>global</literal> 指令创建的是相同的。
- 要注意在数据模型中的变量作为page范围的属性对JSP标签是不可见的,尽管它们在全局是可见的,
- 因为数据模型和请求,会话,应用范围是对应的,而不是page范围。</para>
-
- <para>在JSP页面中,你可以对所有属性值加引号,这和参数类型是字符串,
- 布尔值或数字没有关系。但是因为在FTL模板中自定义标签可以被用户自定义FTL指令访问到,
- 你将不得不在自定义标签中使用FTL语法规则,而不是JSP语法。所以当你指定一个"属性"的值时,
- 那么在 <literal>=</literal> 的右边是一个 <link
- linkend="dgui_template_exp">FTL 表达式</link>。因此,
- <emphasis>你不能对布尔值和数字值的参数加引号</emphasis>
- (比如:<literal><@tiles.insert page="/layout.ftl" flush=true/></literal>),
- 否则它们将被解释为字符串值,当FreeMarker试图传递值到期望非字符串值的自定义标记中时,
- 这就会引起类型不匹配错误。而且还要注意,这很自然,你可以使用任意FTL表达式作为属性的值,
- 比如变量,计算的结果值等。(比如:<literal><@tiles.insert page=layoutName
- flush=foo && bar/></literal>)</para>
-
- <para>Servlet容器运行过程中,因为它实现了自身的轻量级JSP运行时环境,
- 它用到JSP标签库,而 FreeMarker 并不依赖于JSP支持。这是一个很小但值得注意的地方:
- 在它们的TLD文件中,开启 FreeMarker 的JSP运行时环境来分发事件到JSP标签库中注册时间监听器,
- 你应该将下面的内容添加到Web应用下的 <literal>WEB-INF/web.xml</literal> 文件中:</para>
-
- <programlisting role="unspecified"><listener>
- <listener-class>freemarker.ext.jsp.EventForwarding</listener-class>
-</listener></programlisting>
-
- <para>请注意,尽管servlet容器没有本地的JSP支持,你也可以在 FreeMarker 中使用JSP标签库。
- 只是确保对JSP 1.2版本(或更新)的 <literal>javax.servlet.jsp.*</literal>
- 包在Web应用程序中可用就行。如果你的servlet容器只对JSP 1.1支持,
- 那么你不得不将下面六个类(比如你可以从Tomcat 5.x或Tomcat 4.x的jar包中提取)复制到Web应用的
- <literal>WEB-INF/classes/<replaceable>...</replaceable></literal>目录下:
- <literal>javax.servlet.jsp.tagext.IterationTag</literal>,
- <literal>javax.servlet.jsp.tagext.TryCatchFinally</literal>,
- <literal>javax.servlet.ServletContextListener</literal>,
- <literal>javax.servlet.ServletContextAttributeListener</literal>,
- <literal>javax.servlet.http.HttpSessionAttributeListener</literal>,
- <literal>javax.servlet.http.HttpSessionListener</literal>。但是要注意,
- 因为容器只支持JSP 1.1,通常是使用较早的Servlet 2.3之前的版本,
- 事件监听器可能就不支持,因此JSP 1.2标签库来注册事件监听器会正常工作。</para>
-
- <para>在撰写本文档时,JSP已经升级到2.1了,许多特性也已经实现了,
- 除了JSP 2(也就是说JSP自定义标记在JSP语言中<emphasis>实现了</emphasis>)的"标签文件"特性。
- 标签文件需要被编译成Java类文件,在 FreeMarker 下才会有用。</para>
-
- <para><literal>JspTaglibs[<replaceable>uri</replaceable>]</literal>
- 会去找到URI指定的TLD,就像JSP的 <literal>@taglib</literal> 指令所做的。
- 它实现了JSP规范中所描述的TLD发现机制。这里可以阅读更多,但简而言之,
- 它会在 <literal>WEB-INF/web.xml</literal> <literal>taglib</literal> 元素中,
- 在 <literal>WEB-INF/**/*.tld</literal> 文件中,还有
- <literal>WEB-INF/lib/*.{jar,zip}/META-INF/**/*.tld</literal> 文件中寻找TLD。
- 此外,当设置了 <literal>FreemarkerServlet</literal> 的初始化参数(从 2.3.22版本开始)
- <literal>MetaInfTldSources</literal> 和/或 <literal>ClasspathTlds</literal>,
- 即便是在WAR结构之外,它也会发现对于类加载器可见的TLD。参考
- <literal>FreemarkerServlet</literal> 的Java API文档来获取更多描述。
- 它也可以从Java系统属性中来设置,当你想在Eclipse运行配置中来修改而不去修改
- <literal>web.xml</literal>时,就可以随手完成;再强调一点,请参考
- <literal>FreemarkerServlet</literal> API 文档。
- <literal>FreemarkerServlet</literal> 也会识别
- <literal>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</literal>
- servlet 上下文属性,并且将它中间的配置项添加到
- <literal>MetaInfTldSources</literal>。</para>
- </section>
-
- <section>
- <title>在JSP页面中嵌入FTL</title>
-
- <indexterm>
- <primary>JSP</primary>
-
- <secondary>taglib</secondary>
- </indexterm>
-
- <para>有一个标签库允许你将FTL片段放到JSP页面中。
- 嵌入的FTL片段可以访问JSP 的4种范围内的属性(Beans)。
- 你可以在 FreeMarker 发布包中找到一个可用的示例和这个标签库。</para>
- </section>
- </section>
-
- <section xml:id="pgui_misc_secureenv">
- <title>为FreeMarker配置安全策略</title>
-
- <indexterm>
- <primary>security</primary>
- </indexterm>
-
- <para>当 FreeMarker 运行在装有安全管理器的Java虚拟机中时,
- 你不得不再授与一些权限,确保运行良好。最值得注意的是,
- 你需要为对 <literal>freemarker.jar</literal>
- 的安全策略文件添加这些条目:</para>
-
- <programlisting role="unspecified">grant codeBase "file:/path/to/freemarker.jar"
-{
- permission java.util.PropertyPermission "file.encoding", "read";
- permission java.util.PropertyPermission "freemarker.*", "read";
-}</programlisting>
-
- <para>另外,如果从一个目录中加载模板,
- 你还需要给 FreeMarker 授权来从那个目录下读取文件,
- 使用如下的授权:</para>
-
- <programlisting role="unspecified">grant codeBase "file:/path/to/freemarker.jar"
-{
- ...
- permission java.io.FilePermission "/path/to/templates/-", "read";
-}</programlisting>
-
- <para>最终,如果你使用默认的模板加载机制,也就是从当前文件夹下加载模板,
- 那么需要指定这些授权内容:(请注意,表达式 <literal>${user.dir}</literal>
- 将会在运行时被策略解释器处理,几乎它就是一个 FreeMarker 模板)</para>
-
- <programlisting role="unspecified">
-grant codeBase "file:/path/to/freemarker.jar"
-{
- ...
- permission java.util.PropertyPermission "user.dir", "read";
- permission java.io.FilePermission "${user.dir}/-", "read";
-}</programlisting>
-
- <para>很自然地,如果你在Windows下运行,
- 使用两个反斜杠来代替一个斜杠来分隔路径中的目录间隔。</para>
- </section>
-
- <section xml:id="pgui_misc_xml_legacy">
- <title>遗留的XML包装实现</title>
-
- <note>
- <para><emphasis>遗留的XML包装已经废弃了。</emphasis>
- FreeMarker 2.3 已经引入了对新的XML处理模型的支持。要支持它,
- 新的XML包装包已经引入了,就是 <literal>freemarker.ext.dom</literal>。
- 对于新用法,我们鼓励你使用。它会在<xref linkend="xgui"/>中来说明。</para>
- </note>
-
- <para><literal>freemarker.ext.xml.NodeListModel</literal>
- 类提供了来包装XML文档展示为结点树模板模型。
- 每个结点列表可以包含零个或多个XML结点
- (文档类型,元素类型,文本类型,处理指令,注释,实体引用,CDATA段等)。
- 结点列表实现了下面模板的语义模型接口:</para>
-
- <section>
- <title>模板标量模型(TemplateScalarModel)</title>
-
- <para>当使用一个标量时,结点列表将会呈现XML片段,表示其包含的结点。
- 这使得使用XML到XML转换模板很方便。</para>
- </section>
-
- <section>
- <title>模板集合模型(TemplateCollectionModel)</title>
-
- <para>当用 <literal>list</literal> 指令来使用一个集合时,
- 它会简单枚举它的结点。每个结点将会被当作一个新的单一结点组的结点列表返回。</para>
- </section>
-
- <section>
- <title>模板序列模型(TemplateSequenceModel)</title>
-
- <para>当被用作是序列时,它会返回第i个结点作为一个新的结点列表,
- 包含单独的被请求的结点。也就是说,要返回 <literal><book></literal>
- 元素的第三个 <literal><chapter></literal> 元素,你可以使用下面的代码
- (结点索引是从零开始的):</para>
-
- <programlisting role="template"><#assign thirdChapter = xmldoc.book.chapter[2]></programlisting>
- </section>
-
- <section>
- <title>模板哈希表模型(TemplateHashModel)</title>
-
- <para>当被用作是哈希表时,它基本上是用来遍历子结点。也就是说,
- 如果你有个名为 <literal>book</literal> 的结点列表,
- 并包装了一个有很多chapter的元素结点,那么 <literal>book.chapter</literal>
- 将会产生一个book元素的所有chapter元素的结点列表。@符号常被用来指代属性:
- <literal>book.@title</literal> 产生一个有单独属性的结点列表,
- 也就是book元素的title属性。</para>
-
- <para>意识到下面这样的结果是很重要的,比如,如果 <literal>book</literal>
- 没有 <literal>chapter</literal>-s,那么 <literal>book.chapter</literal>
- 就是一个空序列,所以 <literal>xmldoc.book.chapter??</literal> 就
- <emphasis>不会</emphasis> 是 <literal>false</literal>,它会一直是
- <literal>true</literal>!相似地,
- <literal>xmldoc.book.somethingTotallyNonsense??</literal>
- 也不会是<literal>false</literal>。为了检查是否发现子结点,可以使用
- <literal>xmldoc.book.chapter?size == 0</literal>。</para>
-
- <para>哈希表定义了一些"魔力键"。所有的这些键以下划线开头。
- 最值得注意的是<literal>_text</literal>,可以得到结点的文本内容:
- <literal>${book.@title._text}</literal> 将会给模板交出属性的值。
- 相似地,<literal>_name</literal>将会取得元素或属性的名字。
- <literal>*</literal> 或 <literal>_allChildren</literal>
- 返回所有结点列表元素中的直接子元素,而 <literal>@*</literal>
- 或 <literal>_allAttributes</literal> 返回结点列表中元素的所有属性。
- 还有很多这样的键;下面给出哈希表键的详细总结:</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>键名</th>
-
- <th>结果为</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>*</literal> 或 <literal>_children</literal></td>
-
- <td>所有当前结点(非递归)的直接子元素。适用于元素和文档结点。</td>
- </tr>
-
- <tr>
- <td><literal>@*</literal> 或
- <literal>_attributes</literal></td>
-
- <td>当前结点的所有属性。仅适用于元素。</td>
- </tr>
-
- <tr>
- <td><literal>@<replaceable>attributeName</replaceable></literal></td>
-
- <td>当前结点的命名属性。适用于元素,声明和处理指令。
- 在声明中它支持属性 <literal>publicId</literal>,<literal>systemId</literal>
- 和 <literal>elementName</literal>。在处理指令中,它支持属性
- <literal>target</literal> 和 <literal>data</literal>,还有数据中以
- <literal>name="value"</literal> 对出现的其他属性名。
- 对于声明和处理指令的属性结点是合成的,因此它们没有父结点。
- 要注意,<literal>@*</literal> 不能在声明或处理指令上进行操作。</td>
- </tr>
-
- <tr>
- <td><literal>_ancestor</literal></td>
-
- <td>当前结点的所有祖先,直到根元素(递归)。
- 适用于类型和 <literal>_parent</literal> 相同的结点类型。</td>
- </tr>
-
- <tr>
- <td><literal>_ancestorOrSelf</literal></td>
-
- <td>当前结点和它的所有祖先结点。
- 适用于和 <literal>_parent</literal> 相同的结点类型。</td>
- </tr>
-
- <tr>
- <td><literal>_cname</literal></td>
-
- <td>当前结点(命名空间URI+本地名称)的标准名称,
- 每个结点(非递归)一个字符串值。适用于元素和属性。</td>
- </tr>
-
- <tr>
- <td><literal>_content</literal></td>
-
- <td>当前结点的全部内容,包括子元素,文本,
- 实体引用和处理指令(非递归)。适用于元素和文档。</td>
- </tr>
-
- <tr>
- <td><literal>_descendant</literal></td>
-
- <td>当前结点的所有递归的子孙元素。适用于文档和元素结点。</td>
- </tr>
-
- <tr>
- <td><literal>_descendantOrSelf</literal></td>
-
- <td>当前结点和它的所有递归的子孙元素。适用于文档和元素结点。</td>
- </tr>
-
- <tr>
- <td><literal>_document</literal></td>
-
- <td>当前结点所属的所有文档类型。适用于所有除文本的结点。</td>
- </tr>
-
- <tr>
- <td><literal>_doctype</literal></td>
-
- <td>当前结点的声明。仅仅适用于文档类型结点。</td>
- </tr>
-
- <tr>
- <td><literal>_filterType</literal></td>
-
- <td>这是一种按类型过滤的模板方法模型。当被调用时,
- 它会产生一个结点列表,仅仅包含它们当前结点,
- 这些结点的类型和传递给它们参数的一种类型相匹配。
- 你应该传递任意数量的字符串给这个方法,
- 其中包含来保持类型的名字。合法的类型名称是:"attribute",
- "cdata","comment","document","documentType",
- "element","entity","entityReference",
- "processingInstruction","text"。</td>
- </tr>
-
- <tr>
- <td><literal>_name</literal></td>
-
- <td>当前结点的名称。每个结点(非递归)一个字符串值。
- 适用于元素和属性(返回它们的本地名称),实体,
- 处理指令(返回它的目标),声明(返回它的public ID)。</td>
- </tr>
-
- <tr>
- <td><literal>_nsprefix</literal></td>
-
- <td>当前结点的命名空间前缀,每个结点(非递归)一个字符串值。
- 适用于元素和属性。</td>
- </tr>
-
- <tr>
- <td><literal>_nsuri</literal></td>
-
- <td>当前结点的命名空间URI,每个结点(非递归)一个字符串值。
- 适用于元素和属性。</td>
- </tr>
-
- <tr>
- <td><literal>_parent</literal></td>
-
- <td>当前结点的父结点。适用于元素,属性,注释,实体,处理指令。</td>
- </tr>
-
- <tr>
- <td><literal>_qname</literal></td>
-
- <td>当前结点在 <literal>[namespacePrefix:]localName</literal>
- 形式的限定名,每个结点(非递归)一个字符串值。适用于元素和属性。</td>
- </tr>
-
- <tr>
- <td><literal>_registerNamespace(prefix, uri)</literal></td>
-
- <td>注册一个对当前结点列表和从当前结点列表派生出的所有结点列表有指定前缀和URI的XML命名空间。
- 注册之后,你可以使用<literal>nodelist["prefix:localname"]</literal> 或
- <literal>nodelist["@prefix:localname"]</literal> 语法来访问元素和属性,
- 它们的名字是命名空间范围内的。
- 注意命名空间的前缀需要不能和当前XML文档它自己使用的前缀相匹配,
- 因为命名空间纯粹是由URI来比较的。</td>
- </tr>
-
- <tr>
- <td><literal>_text</literal></td>
-
- <td>当前结点的文本内容,每个结点(非递归)一个字符串值。
- 适用于元素,属性,注释,处理指令(返回它的数据)和CDATA段。
- 保留的XML字符('<'和'&')不能被转义。</td>
- </tr>
-
- <tr>
- <td><literal>_type</literal></td>
-
- <td>返回描述结点类型的结点列表,每个结点包含一个字符串值。
- 可能的结点名称是:合法的结点名称是:"attribute","cdata","comment",
- "document","documentType","element","entity","entityReference",
- "processingInstruction","text"。如果结点类型是未知的,就返回"unknown"。</td>
- </tr>
-
- <tr>
- <td><literal>_unique</literal></td>
-
- <td>当前结点的一个拷贝,仅仅保留每个结点第一次的出现,消除重复。
- 重复可以通过应用对树的向上遍历出现在结点列表中,如<literal>_parent</literal>,
- <literal>_ancestor</literal>,<literal>_ancestorOrSelf</literal> 和
- <literal>_document</literal>,也就是说,<literal>foo._children._parent</literal>
- 会返回一个结点列表,它包含foo中重复的结点,每个结点会包含出现的次数,
- 和它子结点数目相等。这些情况下,使用 <literal>foo._children._parent._unique</literal>
- 来消除重复。适用于所有结点类型。</td>
- </tr>
-
- <tr>
- <td>其它键</td>
-
- <td>当前结点的子元素的名称和键相匹配。这允许以
- <literal>book.chapter.title</literal> 这种风格语法进行方便的子元素遍历。
- 请注意,在技术上 <literal>nodeset.childname</literal> 和
- <literal>nodeset("childname")</literal> 相同,但是两者写法都很短,
- 处理也很迅速。适用于文档和元素结点。</td>
- </tr>
- </tbody>
- </informaltable>
- </section>
-
- <section>
- <title>模板方法模型(TemplateMethodModel)</title>
-
- <para>当被用作方法模型,它返回一个结点列表,
- 这个列表是处理结点列表中当前内容的XPath表达式的结果。
- 为了使这种特性能够工作,你必须将 <literal>Jaxen</literal>
- 类库放到类路径下。比如:</para>
-
- <programlisting role="template"><#assign firstChapter=xmldoc("//chapter[first()]")></programlisting>
- </section>
-
- <section>
- <title>命名空间处理</title>
-
- <para>为了遍历有命名空间范围内名称的子元素这个目的,
- 你可以使用结点列表注册命名空间前缀。
- 你可以在Java代码中来做,调用:</para>
-
- <programlisting role="unspecified">public void registerNamespace(String prefix, String uri);</programlisting>
-
- <para>方法,或者在模板中使用</para>
-
- <programlisting role="template">${<replaceable>nodelist</replaceable>._registerNamespace(<replaceable>prefix</replaceable>, <replaceable>uri</replaceable>)}</programlisting>
-
- <para>语法。从那里开始,
- 你可以在命名空间通过特定的URI来标记引用子元素,用这种语法</para>
-
- <programlisting role="metaTemplate"><literal><replaceable>nodelist</replaceable>["<replaceable>prefix</replaceable>:<replaceable>localName</replaceable>"]</literal></programlisting>
-
- <para>和</para>
-
- <programlisting role="metaTemplate"><literal><replaceable>nodelist</replaceable>["@<replaceable>prefix</replaceable>:<replaceable>localName</replaceable>"]</literal></programlisting>
-
- <para>和在 XPath 表达式中使用这些命名空间前缀一样。
- 命名空间使用一个结点列表来注册并传播到所有结点列表,
- 这些结点列表来自于原来的结点列表。要注意命名空间只可使用URI来进行匹配,
- 所以你可以在你的模板中安全地使用命名空间的前缀,这和在实际XML中的不同,
- 在模板和XML文档中,一个前缀只是一个对URI的本地别名。</para>
- </section>
- </section>
-
- <section xml:id="pgui_misc_ant">
- <title>和Ant一起使用FreeMarker</title>
-
- <indexterm>
- <primary>ant task</primary>
- </indexterm>
-
- <para>我们现在知道有两种"FreeMarker Ant tasks":</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>FreemarkerXmlTask</literal>:它来自于FreeMarker的发布包,
- 打包到 <literal>freemarker.jar</literal> 中。
- 这是使用FreeMarker模板转换XML文档的轻量级的,易于使用的Ant任务。
- 它的入口源文件(输入文件)是XML文件,和生成的输出文件对应,
- 这是通过单独模板实现的。也就是说,对于每个XML文件,
- 模板会被执行(在数据模型中的XML文档),
- 模板的输出会被写入到一个和原XML文件名相似名称的文件中。
- 因此,模板文件扮演了一个和XSLT样式表相似的角色,但它是FTL,而不是XSLT。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>command-line</primary>
- </indexterm>FMPP:这是一个重量级的,以很少的XML为中心,
- 第三方Ant任务(和独立的命令行工具)。
- 它主要的目的是用作为模板文件的源文件(输入文件)生成它们自己对应的输出文件,
- 但它也对以XML为源文件的 <literal>FreemarkerXmlTask</literal> 进行支持。
- 而且,相比于<literal>FreemarkerXmlTask</literal>,它还有额外的特性。
- 那么它的缺点是什么?它太复杂太一般化了,不容易掌握和使用。</para>
- </listitem>
- </itemizedlist>
-
- <para>这一部分介绍了 <literal>FreemarkerXmlTask</literal>,
- 要了解FMPP更多的信息,可以访问它的主页:<link
- xlink:href="http://fmpp.sourceforge.net/">http://fmpp.sourceforge.net/</link>。</para>
-
- <para>为了使用 <literal>FreemarkerXmlTask</literal>,
- 首先必须在你的Ant构建文件中定义
- <literal>freemarker.ext.ant.FreemarkerXmlTask</literal>,然后调用任务。
- 假设你想使用假定的"xml2html.ftl"模板转换一些XML文档到HTML,
- XML文档在目录"xml"中而HTML文档生成到目录"html"中,你应该这样来写:</para>
-
- <programlisting role="unspecified"><taskdef name="freemarker" classname="freemarker.ext.ant.FreemarkerXmlTask">
- <classpath>
- <pathelement location="freemarker.jar" />
- </classpath>
-</taskdef>
-<mkdir dir="html" />
-<freemarker basedir="xml" destdir="html" includes="**/*.xml" template="xml2html.ftl" /></programlisting>
-
- <para>这个任务将会对每个XML文档调用模板。每个文档将会被解析成DOM树,
- 然后包装成FreeMarker结点变量。当模板开始执行时,
- 特殊变量 <literal>.node</literal> 被设置成XML文档结点的根root。</para>
-
- <para>请注意,如果你正使用遗留的(FreeMarker 2.2.x 和以前版本)XML适配器实现,
- 也同样可以进行,而且XML树的根结点被放置在数据模型中,
- 作为变量 <literal>document</literal>。它包含了遗留的
- <literal>freemarker.ext.xml.NodeListModel</literal> 类的实例。</para>
-
- <para>请注意,所有通过构建文件定义的属性将会作为名为"properties"的哈希表模型来用。
- 一些其他方法也会可用;对模板中什么样的可用变量的详细描述,
- 还有什么样的属性可以被任务接受,参见
- <literal>freemarker.ext.ant.FreemarkerXmlTask</literal> 的JavaDoc文档。</para>
- </section>
-
- <section xml:id="pgui_misc_jythonwrapper">
- <title>Jython包装器</title>
-
- <indexterm>
- <primary>wrapping</primary>
-
- <secondary>jython</secondary>
- </indexterm>
-
- <indexterm>
- <primary>jython</primary>
-
- <secondary>wrapping</secondary>
- </indexterm>
-
- <para><literal>freemarker.ext.jython</literal>
- 包包含了启用任意Jython对象的模型,
- 并被用作是<literal>TemplateModel</literal>。在一个基础的示例中,
- 你可以使用如下调用:</para>
-
- <programlisting role="unspecified">public TemplateModel wrap(Object obj);</programlisting>
-
- <para><literal>freemarker.ext.jython.JythonWrapper</literal> 类的方法。
- 这个方法会包装传递的对象,包装成合适的 <literal>TemplateModel</literal>。
- 下面是一个对返回对象包装器的属性的总结。为了下面的讨论,
- 我们假设在模板模型根中,对对象 <literal>obj</literal> 调用
- <literal>JythonWrapper</literal> 后模型名为 <literal>model</literal>。</para>
-
- <section>
- <title>模板哈希表模型功能(TemplateHashModel functionality)</title>
-
- <para><literal>PyDictionary</literal> 和
- <literal>PyStringMap</literal> 将会被包装成一个哈希表模型。
- 键的查找映射到 <literal>__finditem__</literal>
- 方法;如果一个项没有被找到,
- 那么就返回一个为 <literal>None</literal> 的模型。</para>
- </section>
-
- <section>
- <title>模板标量模型功能(TemplateScalarModel functionality)</title>
-
- <para>每一个python对象会实现
- <literal>TemplateScalarModel</literal> 接口,
- 其中的 <literal>getAsString()</literal>
- 方法会委派给 <literal>toString()</literal> 方法。</para>
- </section>
-
- <section>
- <title>模板布尔值模型功能(TemplateBooleanModel functionality)</title>
-
- <para>每一个python对象会实现
- <literal>TemplateBooleanModel</literal> 接口,
- 其中的 <literal>getAsBoolean()</literal> 方法会指派给
- <literal>__nonzero__()</literal> 方法,
- 符合Python语义的true/false。</para>
- </section>
-
- <section>
- <title>模板数字模型功能(TemplateNumberModel functionality)</title>
-
- <para>对<literal>PyInteger</literal>,<literal>PyLong</literal> 和
- <literal>PyFloat</literal> 对象的模型包装器实现了
- <literal>TemplateNumberModel</literal> 接口,其中的
- <literal>getAsNumber()</literal> 方法返回
- <literal>__tojava__(java.lang.Number.class)</literal>。</para>
- </section>
-
- <section>
- <title>模板序列模型功能(TemplateSequenceModel functionality)</title>
-
- <para>对所有扩展了 <literal>PySequence</literal> 的类的模型包装器会实现
- <literal>TemplateSequenceModel</literal> 接口,
- 因此它们中的元素可以通过使用 <literal>model[i]</literal> 语法形式的序列来访问,
- 这会指派给<literal>__finditem__(i)</literal>。你也可以使用内建函数
- <literal>model?size</literal> 查询数组的长度或者list的大小,
- 它会指派给 <literal>__len__()</literal>。</para>
- </section>
- </section>
- </chapter>
- </part>
-
- <part xml:id="ref">
- <title>模板语言参考 </title>
-
- <chapter xml:id="ref_builtins">
- <title>内建函数参考</title>
-
- <indexterm>
- <primary>built-in</primary>
- </indexterm>
-
- <section xml:id="ref_builtins_alphaidx">
- <title>字母顺序索引</title>
-
- <note>
- <para>在 FreeMarker 2.3.23 中,指令名可以使用驼峰样式来代替蛇形样式,
- 比如 <literal>startsWith</literal> 代替 <literal>starts_with</literal>。
- 但是要知道,在相同模版内,FreeMarker 会强制对模板语言部分的所有标识符使用驼峰样式
- (用户自定义名称不会受影响)。</para>
- </note>
-
- <indexterm>
- <primary>built-in</primary>
- </indexterm>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="ref_builtin_abs">abs</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_ancestors">ancestors</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_buitin_api_and_has_api">api</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_boolean">boolean</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">byte</link></para>
- </listitem>
-
- <listitem>
- <para>c <link linkend="ref_builtin_c">for strings</link>, <link
- linkend="ref_builtin_c_boolean">for booleans</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_cap_first">cap_first</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_capitalize">capitalize</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_rounding">ceiling</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_children">children</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_chop_linebreak">chop_linebreak</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_chunk">chunk</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_contains">contains</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_counter">counter</link></para>
- </listitem>
-
- <listitem>
- <para>date <link linkend="ref_builtin_date_datetype">for
- dates</link>, <link linkend="ref_builtin_string_date">for
- strings</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_date_if_unknown">date_if_unknown</link></para>
- </listitem>
-
- <listitem>
- <para>datetime <link linkend="ref_builtin_date_datetype">for
- dates</link>, <link linkend="ref_builtin_string_date">for
- strings</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_date_if_unknown">datetime_if_unknown</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">double</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_ends_with">ends_with</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_ensure_ends_with">ensure_ends_with</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_ensure_starts_with">ensure_starts_with</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_eval">eval</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_first">first</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_rounding">floor</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_groups">groups</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">float</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_buitin_api_and_has_api">has_api</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_has_content">has_content</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_has_next">has_next</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_html">html</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_index">index</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_index_of">index_of</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">int</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_interpret">interpret</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_item_cycle">item_cycle</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_item_parity">item_parity</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_item_parity_cap">item_parity_cap</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_is_even_item">is_even_item</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_first">is_first</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_is_infinite">is_infinite</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_is_last">is_last</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_is_nan">is_nan</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_is_odd_item">is_odd_item</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_isType">is_<replaceable>type</replaceable></link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_date_iso">iso,
- iso_...</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_j_string">j_string</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_join">join</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_js_string">js_string</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_keep_after">keep_after</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_keep_after_last">keep_after_last</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_keep_before">keep_before</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_keep_before_last">keep_before_last</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_keys">keys</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_last">last</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_last_index_of">last_index_of</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_left_pad">left_pad</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_length">length</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">long</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_lower_abc">lower_abc</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_lower_case">lower_case</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_matches">matches</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_namespace">namespace</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_new">new</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_node_namespace">node_namespace</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_node_name">node_name</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_node_type">node_type</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_number">number</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numToDate">number_to_date,
- number_to_datetime, number_to_time</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_parent">parent</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_replace">replace</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_remove_beginning">remove_beginning</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_remove_ending">remove_ending</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_reverse">reverse</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_right_pad">right_pad</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_rounding">round</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_root">root</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_rtf">rtf</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_numType">short</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_size">size</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_sort">sort</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_seq_contains">seq_contains</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_seq_index_of">seq_index_of</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_seq_last_index_of">seq_last_index_of</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_sort_by">sort_by</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_split">split</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_starts_with">starts_with</link></para>
- </listitem>
-
- <listitem>
- <para>string: <link linkend="ref_builtin_string_for_string">for
- strings</link>, <link linkend="ref_builtin_string_for_number">for
- numbers</link>, <link linkend="ref_builtin_string_for_boolean">for
- booleans</link>, <link linkend="ref_builtin_string_for_date">for
- date/time/date-time</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_substring">substring</link>
- (deprecated)</para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_switch">switch</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_then">then</link></para>
- </listitem>
-
- <listitem>
- <para>time <link linkend="ref_builtin_date_datetype">for
- date/time/date-time</link>, <link
- linkend="ref_builtin_string_date">for strings</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_date_if_unknown">time_if_unknown</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_trim">trim</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_uncap_first">uncap_first</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_upper_abc">upper_abc</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_upper_case">upper_case</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_url">url</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_values">values</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref_builtin_word_list">word_list</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_xhtml">xhtml</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtin_xml">xml</link></para>
- </listitem>
- </itemizedlist>
-
- <para>点击 <link
- linkend="ref_builtins">这里</link> 参考按照表达式左侧值类型分类的内建函数。</para>
-
- <para>如果在这里没有发现模板中的内建函数,那么可能会在这里找到:<xref
- linkend="ref_deprecated"/></para>
- </section>
-
- <section xml:id="ref_builtins_string">
- <title>字符串内建函数</title>
-
- <indexterm>
- <primary>string</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <para>这些内建函数作用于表达式左侧的字符串值。
- 如果左侧值是数字或日期/时间/日期-时间或布尔类型(从 2.3.20 版本开始),
- 根据当前的number-, date/time/date-time- 和 boolean-format设置,
- 那么它会自动被转成字符串值(当使用<literal>${<replaceable>...</replaceable>}</literal>
- 插入这些值时,应用的都是一样的格式程序)。</para>
-
- <section xml:id="ref_builtin_boolean">
- <title>boolean</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>boolean built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>string to boolean</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting string to boolean</primary>
- </indexterm>
-
- <indexterm>
- <primary>parse string as boolean</primary>
- </indexterm>
-
- <para>字符串转为布尔值。字符串必须是
- <literal>true</literal> 或 <literal>false</literal>
- (大小写敏感!),或者必须是由 <literal>boolean_format</literal>
- 设置的特定格式。</para>
-
- <para>如果字符串不是适当的格式,那么当访问该内建函数时,
- 就会发成错误终止模板处理。</para>
- </section>
-
- <section xml:id="ref_builtin_cap_first">
- <title>cap_first</title>
-
- <indexterm>
- <primary>cap_first built-in</primary>
- </indexterm>
-
- <para>字符串中的首单词的首字母大写。
- 关于"单词"的准确意义,可以参考 <link
- linkend="ref_builtin_word_list">word_list 内建函数</link>。
- 例如:</para>
-
- <programlisting role="template">${" green mouse"?cap_first}
-${"GreEN mouse"?cap_first}
-${"- green mouse"?cap_first}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Green mouse
-GreEN mouse
-- green mouse</programlisting>
-
- <para>In the case of <literal>"- green mouse"</literal>, the first
- word is the <literal>-</literal>.</para>
- </section>
-
- <section xml:id="ref_builtin_capitalize">
- <title>capitalize</title>
-
- <indexterm>
- <primary>capitalize built-in</primary>
- </indexterm>
-
- <para>字符串中所有单词的首字母大写。
- 关于"单词"的准确意义,可以参考 <link
- linkend="ref_builtin_word_list">word_list 内建函数</link>。例如:</para>
-
- <programlisting role="template">${" green mouse"?capitalize}
-${"GreEN mouse"?capitalize}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Green Mouse
-Green Mouse</programlisting>
- </section>
-
- <section xml:id="ref_builtin_chop_linebreak">
- <title>chop_linebreak</title>
-
- <indexterm>
- <primary>chop_linebreak built-in</primary>
- </indexterm>
-
- <para>在末尾没有 <link
- linkend="gloss.lineBreak">换行符</link> 的字符串,
- 那么可以换行,否则不改变字符串。</para>
- </section>
-
- <section xml:id="ref_builtin_contains">
- <title>contains</title>
-
- <indexterm>
- <primary>contains built-in</primary>
- </indexterm>
-
- <note>
- <para>这个内建函数从 FreeMarker 2.3.1 版本开始可用。
- 在2.3版本中是没有的。</para>
- </note>
-
- <para>如果函数中的参数指定的子串出现在源字符串中,
- 那么返回true。比如:</para>
-
- <programlisting role="template"><#if "piceous"?contains("ice")>It contains "ice"</#if>
-</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">It contains "ice"</programlisting>
- </section>
-
- <section xml:id="ref_builtin_string_date">
- <title>date, time, datetime</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>date built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>time built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>datetime built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>string to date</primary>
- </indexterm>
-
- <indexterm>
- <primary>string to time</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting string to date</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting string to time</primary>
- </indexterm>
-
- <indexterm>
- <primary>parsing string as date</primary>
- </indexterm>
-
- <indexterm>
- <primary>parsing string as time</primary>
- </indexterm>
-
- <indexterm>
- <primary>XML Schema date parsing</primary>
- </indexterm>
-
- <indexterm>
- <primary>XML Schema time parsing</primary>
- </indexterm>
-
- <indexterm>
- <primary>ISO 8601 parsing</primary>
- </indexterm>
-
- <para>字符串转换成日期值,时间或日期-时间值。需要一个由 <link
- linkend="topic.dateTimeFormatSettings"><literal>date_format</literal>,
- <literal>time_format</literal> 和
- <literal>datetime_format</literal> 设置</link>指定的格式。
- 如果字符串不是适当的格式,那么当访问该内建函数时,
- 就会发生错误中止模板的处理。</para>
-
- <programlisting role="template"><#-- The date_format, time_format and datetime_format settings must match this format! -->
-<#assign someDate = "Oct 25, 1995"?date>
-<#assign someTime = "3:05:30 PM"?time>
-<#assign someDatetime = "Oct 25, 1995 03:05:00 PM"?datetime>
-
-<#-- Changing the setting value changes the expected format: -->
-<#setting datetime_format="iso">
-<#assign someDatetime = "1995-10-25T15:05"?datetime>
-</programlisting>
-
- <para>也可以指定明确的格式,比如
- <literal>?datetime.<replaceable>format</replaceable></literal> 或
- <literal>?datetime["<replaceable>format</replaceable>"]</literal>
- (由于历史原因,也可以是
- <literal>?datetime("<replaceable>format</replaceable>")</literal>),
- 它们与 <literal>?date</literal> 和 <literal>?time</literal>是相同的。
- 对于格式化值的语法和意义,可以参考 <link
- linkend="topic.dateTimeFormatSettings"><literal>date_format</literal>,
- <literal>time_format</literal> 和
- <literal>datetime_format</literal> 设置</link> 的可能的值。 比如:</para>
-
- <programlisting role="template"><#-- Parsing XML Schema xs:date, xs:time and xs:dateTime values: -->
-<#assign someDate = "1995-10-25"?date.xs>
-<#assign someTime = "15:05:30"?time.xs>
-<#assign someDatetime = "1995-10-25T15:05:00"?datetime.xs>
-
-<#-- Parsing ISO 8601 (both extended and basic formats): -->
-<#assign someDatetime = "1995-10-25T15:05"?datetime.iso>
-<#assign someDatetime = "19951025T1505"?datetime.iso>
-
-<#-- Parsing with SimpleDateFormat patterns: -->
-<#assign someDate = "10/25/1995"?date("MM/dd/yyyy")>
-<#assign someTime = "15:05:30"?time("HH:mm:ss")>
-<#assign someDatetime = "1995-10-25 03:05 PM"?datetime("yyyy-MM-dd hh:mm a")></programlisting>
-
- <para>避免误解,左侧值无需是字符串文本。比如,当从XML结点
- (此处的所有值都是未被解析的字符串)读取数据,那么就需要这样来做
- <literal>order.confirmDate?date.xs</literal>,将字符串转化成真实的日期。</para>
-
- <para>当然,格式也可以是一个变量,比如:
- <literal>"<replaceable>...</replaceable>"?datetime[myFormat]</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_ends_with">
- <title>ends_with</title>
-
- <indexterm>
- <primary>ends_with built-in</primary>
- </indexterm>
-
- <para>返回是否这个字符串以参数中指定的子串结尾。
- 比如 <literal>"ahead"?ends_with("head")</literal> 返回布尔值
- <literal>true</literal>。<literal>"head"?ends_with("head")</literal>
- 也返回 <literal>true</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_ensure_ends_with">
- <title>ensure_ends_with</title>
-
- <indexterm>
- <primary>ensure_ends_with built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用</para>
- </note>
-
- <para>如果字符串没有以第一个参数指定的子串结尾,
- 那么就会将它加到字符串后面,否则返回原字符串。比如,
- <literal>"foo"?ensure_ends_with("/")</literal> 和
- <literal>"foo/"?ensure_ends_with("/")</literal> 返回
- <literal>"foo/"</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_ensure_starts_with">
- <title>ensure_starts_with</title>
-
- <indexterm>
- <primary>ensure_ends_with built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用</para>
- </note>
-
- <para>如果字符串没有以第一个参数指定的子串开头,
- 那么就会将它加到字符串开头,否则返回原字符串。比如,
- <literal>"foo"?ensure_starts_with("/")</literal> 和
- <literal>"/foo"?ensure_starts_with("/")</literal> 返回
- <literal>"/foo"</literal>。</para>
-
- <para>如果指定两个参数,那么第一个参数就被解释成Java正则表达式,
- 如果它不匹配字符串的开头,那么第二个参数指定的字符串就会添加到字符串开头。
- 比如 <literal>someURL?ensure_starts_with("[a-zA-Z]+://", "http://")</literal>
- 就会检查如果字符串是否以 <literal>"[a-zA-Z]+://"</literal> 开头
- (请注意,不需要 <literal>^</literal>),如果不是的话,就添加
- <literal>"http://"</literal>。</para>
-
- <para>该方法也接受第三个<link
- linkend="ref_builtin_string_flags">标志位参数</link>。因为调用两个参数暗指
- <literal>"r"</literal>(也就是正则表达式模式),那么就需要第三个参数了。
- 值得注意的一点是当不需要第一参数被解释成正则表达式,而只是普通文本,
- 但是又想让比较是大小写敏感的,那么此时就需要使用 <literal>"i"</literal>
- 作为第三个参数。</para>
- </section>
-
- <section xml:id="ref_builtin_groups">
- <title>groups</title>
-
- <indexterm>
- <primary>groups built-in</primary>
- </indexterm>
-
- <para>这个函数只作用于内建函数 <literal>matches</literal>
- 的结果。请参考 <link
- linkend="ref_builtin_matches">这里...</link>。</para>
- </section>
-
- <section xml:id="ref_builtin_html">
- <title>html</title>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>output</secondary>
- </indexterm>
-
- <indexterm>
- <primary>html built-in</primary>
- </indexterm>
-
- <para>字符串按照HTML标记输出。也就是说,下面字符串将会被替代:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><</literal> 替换为
- <literal>&lt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>></literal> 替换为
- <literal>&gt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>&</literal> 替换为
- <literal>&amp;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"</literal> 替换为
- <literal>&quot;</literal></para>
- </listitem>
-
- <listitem>
- <para>如果程序员设置了FreeMarker(将 <literal>incompatible_improvements</literal>
- 设置为 <literal>2.3.20</literal> 或更高;更多内容请参考 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setIncompatibleImprovements%28freemarker.core.Version%29">这里</link>),那么
- <literal>'</literal> 被替换为
- <literal>&#39;</literal>。</para>
- </listitem>
- </itemizedlist>
-
- <para>请注意,如果想安全地插入一个属性,
- 必须在HTML模板中使用引号标记(是
- <literal>"</literal>,而不是 <literal>'</literal>)为属性值加引号:</para>
-
- <programlisting role="template"><input type=text name=user value=<emphasis>"</emphasis>${user?html}<emphasis>"</emphasis>></programlisting>
-
- <para>请注意,在HTML页面中,通常想对所有插值使用这个内建函数。
- 所以可以使用 <link
- linkend="ref_directive_escape"><literal>escape</literal>
- 指令</link> 来节约很多输入,减少偶然错误的机会。</para>
- </section>
-
- <section xml:id="ref_builtin_index_of">
- <title>index_of</title>
-
- <indexterm>
- <primary>index_of built-in</primary>
- </indexterm>
-
- <para>返回第一次字符串中出现子串时的索引位置。
- 例如 <literal>"abcabc"?index_of("bc")</literal> 将会返回1
- (不要忘了第一个字符的索引是0)。而且,你可以指定开始搜索的索引位置:
- <literal>"abcabc"?index_of("bc", 2)</literal> 将会返回4。
- 这对第二个参数的数值没有限制:如果它是负数,那就和是0是相同效果了,
- 如果它比字符串的长度还大,那么就和它是字符串长度那个数值是一个效果。
- 小数会被切成整数。</para>
-
- <para>如果第一个参数作为子串没有在该字符串中出现时
- (如果你使用了第二个参数,那么就从给定的序列开始),那么就返回-1。</para>
- </section>
-
- <section xml:id="ref_builtin_j_string">
- <title>j_string</title>
-
- <indexterm>
- <primary>j_string built-in</primary>
- </indexterm>
-
- <para>根据Java语言字符串转义规则来转义字符串,
- 所以它很安全的将值插入到字符串类型中。要注意它
- <emphasis>不会</emphasis> 在被插入的值的两侧添加引号;
- 你需要在字符串值 <emphasis>内部</emphasis> 来使用。</para>
-
- <para>所有 <link linkend="gloss.UCS">UCS</link> 编码下指向0x20的字符会被转义。
- 当它们在Java语言中(比如<literal>\n</literal>,<literal>\t</literal>等)
- 没有专门的转义序列时,将会被用UNICODE进行转义替换
- (<literal>\u<replaceable>XXXX</replaceable></literal>)。</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign beanName = 'The "foo" bean.'>
-String BEAN_NAME = "${beanName?j_string}";</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">String BEAN_NAME = "The \"foo\" bean.";</programlisting>
- </section>
-
- <section xml:id="ref_builtin_js_string">
- <title>js_string</title>
-
- <indexterm>
- <primary>js_string built-in</primary>
- </indexterm>
-
- <para>根据JavaScript语言字符串转义规则来转义字符串,
- 所以它很安全的将值插入到字符串类型中。要注意,
- 它不会在被插入的值两侧添加引号;
- 你需要在字符串值 <emphasis>内部</emphasis> 来使用。</para>
-
- <para>引号(<literal>"</literal>)和单引号(<literal>'</literal>)要被转义。
- 从 FreeMarker 2.3.1 开始,也要将 <literal>></literal>
- 转义为 <literal>\></literal>(为了避免 <literal></script></literal>)。</para>
-
- <para>所有在 <link linkend="gloss.UCS">UCS</link>
- 编码下指向0x20的字符将会被转义。
- 当它们在JavaScript中没有专用的转义序列时
- (比如 <literal>\n</literal>,<literal>\t</literal> 等),
- 它们会被UNICODE字符代替(<literal>\u<replaceable>XXXX</replaceable></literal>)。</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign user = "Big Joe's \"right hand\"">
-<script>
- alert("Welcome ${user?js_string}!");
-</script></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"><script>
- alert("Welcome Big Joe\'s \"right hand\"!");
-</script></programlisting>
- </section>
-
- <section xml:id="ref_builtin_json_string">
- <title>json_string</title>
-
- <indexterm>
- <primary>json_string built-in</primary>
- </indexterm>
-
- <para>根据JSON语言的字符串规则来转义字符串,
- 所以在字符串中插入值是安全的。
- 要注意它 <emphasis>不会</emphasis> 在被插入的值两侧添加引号;
- 你需要在字符串值 <emphasis>内部</emphasis> 来使用。</para>
-
- <para>这不会转义 <literal>'</literal> 字符,因为JSON字符串必须使用
- <literal>"</literal> 来括起来。它会在 <literal><</literal>
- 之后直接出现的 <literal>/</literal>(斜杠)字符转义为 <literal>\/</literal>,
- 来避免 <literal></script></literal> 等。
- 它也会在 <literal>]]</literal> 之后转义 <literal>></literal>
- 字符为 <literal>\u003E</literal>,来避免退出XML的 <literal>CDATA</literal> 段。</para>
-
- <para>所有在 <link linkend="gloss.UCS">UCS</link> 编码下指向0x20的字符会被转义。
- 当在JSON中没有专用的转义序列时
- (比如 <literal>\n</literal>,<literal>\t</literal> 等),
- 它们会被UNICODE字符代替(<literal>\u<replaceable>XXXX</replaceable></literal>)。</para>
- </section>
-
- <section xml:id="ref_builtin_keep_after">
- <title>keep_after</title>
-
- <indexterm>
- <primary>keep_after built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用。</para>
- </note>
-
- <para>移除字符串中的一部分内容,该部分是给定子串第一次出现之前的部分。
- 比如:</para>
-
- <programlisting role="template">${"abcdefgh"?keep_after("de")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">fgh</programlisting>
-
- <para>如果参数字符串没有找到,它会返回空串。如果参数是长度为0的字符串,
- 它会返回源字符串,不会改变。</para>
-
- <para>该方法接受可选的 <link
- linkend="ref_builtin_string_flags">标志位参数</link>,作为它的第二个参数:</para>
-
- <programlisting role="template">${"foo : bar"?keep_after(r"\s*:\s*", "r")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">bar</programlisting>
- </section>
-
- <section xml:id="ref_builtin_keep_after_last">
- <title>keep_after_last</title>
-
- <indexterm>
- <primary>keep_after_last built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.22 版本开始可用。</para>
- </note>
-
- <para>和 <link
- linkend="ref_builtin_keep_after"><literal>keep_after</literal></link> 相同,
- 但是它会保留参数最后一次出现后的部分,而不是第一次。比如:</para>
-
- <programlisting role="template">${"foo.bar.txt"?keep_after_last(".")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">txt</programlisting>
-
- <para>若使用 <literal>keep_after</literal> 则会得到
- <literal>bar.txt</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_keep_before">
- <title>keep_before</title>
-
- <indexterm>
- <primary>keep_before built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用。</para>
- </note>
-
- <para>移除字符串的一部分,该部分是从给定子串开始的部分。
- 比如:</para>
-
- <programlisting role="template">${"abcdef"?keep_before("de")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">abc</programlisting>
-
- <para>如果参数字符串没有找到,它会返回源字符串,不会改变。
- 如果参数是长度为0的字符串,它会返回空串。</para>
-
- <para>该方法接受可选的 <link
- linkend="ref_builtin_string_flags">标志位参数</link>,作为它的第二个参数:</para>
-
- <programlisting role="template">${"foo : bar"?keep_before(r"\s*:\s*", "r")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">foo</programlisting>
- </section>
-
- <section xml:id="ref_builtin_keep_before_last">
- <title>keep_before_last</title>
-
- <indexterm>
- <primary>keep_before_last built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.22 版本开始可用。</para>
- </note>
-
- <para>和 <link
- linkend="ref_builtin_keep_before"><literal>keep_before</literal></link> 相同,
- 但是保留参数最后一次出现之前的部分,而不是第一次出现之前。比如:</para>
-
- <programlisting role="template">${"foo.bar.txt"?keep_before_last(".")}</programlisting>
-
- <para>将会输出</para>
-
- <programlisting role="output">foo.bar</programlisting>
-
- <para>若使用 <literal>keep_before</literal> 则会得到
- <literal>foo</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_last_index_of">
- <title>last_index_of</title>
-
- <indexterm>
- <primary>last_index_of built-in</primary>
- </indexterm>
-
- <para>返回最后一次(最右边)字符串中出现子串时的索引位置。
- 它返回子串第一个(最左边)字符所在位置的索引。例如:
- <literal>"abcabc"?last_index_of("ab")</literal>:将会返回3。
- 而且可以指定开始搜索的索引。例如,
- <literal>"abcabc"?last_index_of("ab", 2)</literal>,将会返回0。
- 要注意第二个参数暗示了子串开始的最大索引。对第二个参数的数值没有限制:
- 如果它是负数,那么效果和是零的一样,如果它比字符串的长度还大,
- 那么就和它是字符串长度那个数值是一个效果。小数会被切成整数。</para>
-
- <para>如果第一个参数作为子串没有在该字符串中出现时
- (如果你使用了第二个参数,那么就从给定的序列开始),那么就返回-1。</para>
- </section>
-
- <section xml:id="ref_builtin_left_pad">
- <title>left_pad</title>
-
- <indexterm>
- <primary>left_pad built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>padding</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。</para>
- </note>
-
- <para>如果它仅仅用1个参数,那么它将在字符串的开始插入空白,
- 直到整个串的长度达到参数指定的值。
- 如果字符串的长度达到指定数值或者比指定的长度还长,
- 那么就什么都不做了。比如这样:</para>
-
- <programlisting role="template">[${""?left_pad(5)}]
-[${"a"?left_pad(5)}]
-[${"ab"?left_pad(5)}]
-[${"abc"?left_pad(5)}]
-[${"abcd"?left_pad(5)}]
-[${"abcde"?left_pad(5)}]
-[${"abcdef"?left_pad(5)}]
-[${"abcdefg"?left_pad(5)}]
-[${"abcdefgh"?left_pad(5)}]</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">[ ]
-[ a]
-[ ab]
-[ abc]
-[ abcd]
-[abcde]
-[abcdef]
-[abcdefg]
-[abcdefgh]</programlisting>
-
- <para>如果使用了两个参数,那么第一个参数表示的含义和你使用一个参数时的相同,
- 第二个参数指定用什么东西来代替空白字符。比如:</para>
-
- <programlisting role="template">[${""?left_pad(5, "-")}]
-[${"a"?left_pad(5, "-")}]
-[${"ab"?left_pad(5, "-")}]
-[${"abc"?left_pad(5, "-")}]
-[${"abcd"?left_pad(5, "-")}]
-[${"abcde"?left_pad(5, "-")}]</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">[-----]
-[----a]
-[---ab]
-[--abc]
-[-abcd]
-[abcde]</programlisting>
-
- <para>第二个参数也可以是个长度比1大的字符串。
- 那么这个字符串会周期性的插入,比如:</para>
-
- <programlisting role="template">[${""?left_pad(8, ".oO")}]
-[${"a"?left_pad(8, ".oO")}]
-[${"ab"?left_pad(8, ".oO")}]
-[${"abc"?left_pad(8, ".oO")}]
-[${"abcd"?left_pad(8, ".oO")}]</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">[.oO.oO.o]
-[.oO.oO.a]
-[.oO.oOab]
-[.oO.oabc]
-[.oO.abcd]</programlisting>
-
- <para>第二个参数必须是个字符串值,而且至少有一个字符。</para>
- </section>
-
- <section xml:id="ref_builtin_length">
- <title>length</title>
-
- <indexterm>
- <primary>length built-in</primary>
- </indexterm>
-
- <para>字符串中字符的数量。</para>
- </section>
-
- <section xml:id="ref_builtin_lower_case">
- <title>lower_case</title>
-
- <indexterm>
- <primary>lower_case built-in</primary>
- </indexterm>
-
- <para>字符串的小写形式。比如
- <literal>"GrEeN MoUsE"?lower_case</literal>
- 将会是 <literal>"green mouse"</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_matches">
- <title>matches</title>
-
- <indexterm>
- <primary>matches built-in</primary>
- </indexterm>
-
- <para>这是一个"超级用户"函数。如果不懂 <link
- linkend="gloss.regularExpression">正则表达式</link>,就忽略它吧。</para>
-
- <para>该内建函数决定了字符串是否精确匹配模式。而且,它会返回匹配子串的列表。
- 返回值是多类型值:</para>
-
- <itemizedlist>
- <listitem>
- <para>布尔值:如果字符串整体匹配了模式,就是 <literal>true</literal>,
- 否则就是 <literal>false</literal>。比如:<literal>"fooo"?matches('fo*')</literal>
- 就是 <literal>true</literal>,但是 <literal>"fooo bar"?matches('fo*')</literal> 是
- <literal>false</literal>。</para>
- </listitem>
-
- <listitem>
- <para>序列:字符串匹配的子串的列表。很有可能是长度为0的序列。</para>
- </listitem>
- </itemizedlist>
-
- <para>比如:</para>
-
- <programlisting role="template"><#if "fxo"?matches("f.?o")>Matches.<#else>Does not match.</#if>
-
-<#assign res = "foo bar fyo"?matches("f.?o")>
-<#if res>Matches.<#else>Does not match.</#if>
-Matching sub-strings:
-<#list res as m>
-- ${m}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Matches.
-
-Does not match.
-Matching sub-strings:
-- foo
-- fyo</programlisting>
-
- <para>如果正则表达式包含分组(圆括号),那么可以使用
- <literal>groups</literal> 内建函数来访问它们:</para>
-
- <programlisting role="template"><#-- Entire input match -->
-<#assign res = "John Doe"?matches(r"(\w+) (\w+)")>
-<#if res> <#-- Must not try to access groups if there was no match! -->
- First name: ${res?groups[1]}
- Second name: ${res?groups[2]}
-</#if>
-
-<#-- Subtring matches -->
-<#assign res = "aa/rx; ab/r;"?matches("(.+?)/*(.+?);")>
-<#list res as m>
- - "${m}" is "${m?groups[1]}" per "${m?groups[2]}"
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> First name: John
- Second name: Doe
-
- - "aa/rx;" is "a" per "a/rx"
- - " ab/r;" is " " per "ab/r"</programlisting>
-
- <para>请注意,上面的 <literal>groups</literal>
- 对子串匹配和整个字符串匹配的结果都起作用。</para>
-
- <para><literal>matches</literal> 接受可选的第二参数,
- <link linkend="ref_builtin_string_flags">标志位</link>。请注意,
- 它不支持标志 <literal>f</literal>,也会忽略
- <literal>r</literal> 标志。</para>
- </section>
-
- <section xml:id="ref_builtin_number">
- <title>number</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>number built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>string to number</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting string to number</primary>
- </indexterm>
-
- <indexterm>
- <primary>parse string as number</primary>
- </indexterm>
-
- <para>字符串转化为数字格式。这个数字必须是
- <quote>计算机语言</quote> 格式。也就是说,
- 它必须是本地化独立的形式,小数的分隔符就是一个点,没有分组。</para>
-
- <para>该内建函数识别FreeMarker模板语言使用的数字格式。此外,
- 它也识别科学记数法(比如 <literal>"1.23E6"</literal>,<literal>"1.5e-8"</literal>)
- 从 FreeMarker 2.3.21 版本开始,它也识别所有XML Schema数字格式,比如
- <literal>NaN</literal>,<literal>INF</literal>,<literal>-INF</literal>,
- 还有Java本地格式<literal>Infinity</literal> 和 <literal>-Infinity</literal>。</para>
-
- <para>如果字符串不是适当的格式,当尝试访问该内建函数时就会发生错误,
- 并中止模板执行。</para>
-
- <para><phrase role="forProgrammers">实际上,字符串是由当前
- <literal>arithmetic_engine</literal> 的 <literal>toNumber</literal>
- 方法解析的,这是可以配置的设置项。不过该方法应该和上面描述的行为相似。
- </phrase></para>
- </section>
-
- <section xml:id="ref_builtin_replace">
- <title>replace</title>
-
- <indexterm>
- <primary>replace built-in</primary>
- </indexterm>
-
- <para>在源字符串中,用另外一个字符串来替换原字符串中出现它的部分。
- 它不处理词的边界。比如:</para>
-
- <programlisting role="template">${"this is a car acarus"?replace("car", "bulldozer")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">this is a bulldozer abulldozerus</programlisting>
-
- <para>替换是从左向右执行的。这就意味着:</para>
-
- <programlisting role="template">${"aaaaa"?replace("aaa", "X")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Xaa</programlisting>
-
- <para>如果第一个参数是空字符串,那么所有的空字符串将会被替换,
- 比如 <literal>"foo"?replace("","|")</literal>,就会得到
- <literal>"|f|o|o|"</literal>。</para>
-
- <para><literal>replace</literal> 接受可选的 <link
- linkend="ref_builtin_string_flags">标志位参数</link>,作为它的第三参数。</para>
- </section>
-
- <section xml:id="ref_builtin_right_pad">
- <title>right_pad</title>
-
- <indexterm>
- <primary>right_pad built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>padding</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。
- 在2.3版本中是没有的。</para>
- </note>
-
- <para>它和 <link
- linkend="ref_builtin_left_pad"><literal>left_pad</literal></link> 相同,
- 但是它从末尾开始插入字符而不是从开头。</para>
-
- <para>比如:</para>
-
- <programlisting role="template">[${""?right_pad(5)}]
-[${"a"?right_pad(5)}]
-[${"ab"?right_pad(5)}]
-[${"abc"?right_pad(5)}]
-[${"abcd"?right_pad(5)}]
-[${"abcde"?right_pad(5)}]
-[${"abcdef"?right_pad(5)}]
-[${"abcdefg"?right_pad(5)}]
-[${"abcdefgh"?right_pad(5)}]
-
-[${""?right_pad(8, ".oO")}]
-[${"a"?right_pad(8, ".oO")}]
-[${"ab"?right_pad(8, ".oO")}]
-[${"abc"?right_pad(8, ".oO")}]
-[${"abcd"?right_pad(8, ".oO")}]</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">[ ]
-[a ]
-[ab ]
-[abc ]
-[abcd ]
-[abcde]
-[abcdef]
-[abcdefg]
-[abcdefgh]
-
-[.oO.oO.o]
-[aoO.oO.o]
-[abO.oO.o]
-[abc.oO.o]
-[abcdoO.o]</programlisting>
- </section>
-
- <section xml:id="ref_builtin_remove_beginning">
- <title>remove_beginning</title>
-
- <indexterm>
- <primary>remove_beginning built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用。</para>
- </note>
-
- <para>从字符串的开头移除参数中的子串,如果它不以参数中的子串开头,
- 那么就或者返回原字符串。比如:</para>
-
- <programlisting role="template">${"abcdef"?remove_beginning("abc")}
-${"foobar"?remove_beginning("abc")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">def
-foobar</programlisting>
- </section>
-
- <section xml:id="ref_builtin_remove_ending">
- <title>remove_ending</title>
-
- <indexterm>
- <primary>remove_ending built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用。</para>
- </note>
-
- <para>从字符串的结尾移除参数中的子串,如果它不以参数中的子串结尾,
- 那么就或者返回原字符串。比如:</para>
-
- <programlisting role="template">${"abcdef"?remove_ending("def")}
-${"foobar"?remove_ending("def")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">abc
-foobar</programlisting>
- </section>
-
- <section xml:id="ref_builtin_rtf">
- <title>rtf</title>
-
- <indexterm>
- <primary>rtf built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>output</secondary>
- </indexterm>
-
- <para>字符串作为富文本(RTF 文本),也就是说,下列字符串:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>\</literal> 替换为
- <literal>\\</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>{</literal> 替换为
- <literal>\{</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>}</literal> 替换为
- <literal>\}</literal></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_builtin_split">
- <title>split</title>
-
- <indexterm>
- <primary>split built-in</primary>
- </indexterm>
-
- <para>它被用来根据另外一个字符串的出现将原字符串分割成字符串序列。
- 比如:</para>
-
- <programlisting role="template"><#list "someMOOtestMOOtext"?split("MOO") as x>
-- ${x}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- some
-- test
-- text</programlisting>
-
- <para>请注意,假设所有的分隔符都在新项之前出现
- (除了使用 <literal>"r"</literal> 标志 - 后面详细介绍)
- 因此:</para>
-
- <programlisting role="template"><#list "some,,test,text,"?split(",") as x>
-- "${x}"
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- "some"
-- ""
-- "test"
-- "text"
-- ""</programlisting>
-
- <para><literal>split</literal> 接受可选的 <link
- linkend="ref_builtin_string_flags">标志位参数</link>,
- 作为它的第二个参数。由于历史使用 <literal>r</literal>
- (正则表达式)标志的差错;它会从结果列表中移除空元素,
- 所以在最后示例中使用 <literal>?split(",", "r")</literal>,
- <literal>""</literal> 会从输出中消失。</para>
-
- <note>
- <para>要检查一个字符串是否以...结尾或者要附加它,
- 使用 <link linkend="ref_builtin_ensure_ends_with">
- <literal>ensure_ends_with</literal> 内建函数</link>。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_starts_with">
- <title>starts_with</title>
-
- <indexterm>
- <primary>starts_with built-in</primary>
- </indexterm>
-
- <para>如果字符串以指定的子字符串开头,那么返回<literal>true</literal>。
- 比如 <literal>"redirect"?starts_with("red")</literal> 返回布尔值
- <literal>true</literal>,而且 <literal>"red"?starts_with("red")</literal>
- 也返回 <literal>true</literal>。</para>
-
- <note>
- <para>要检查一个字符串是否以...开头或者要在前面附加它,
- 使用 <link linkend="ref_builtin_ensure_starts_with">
- <literal>ensure_starts_with</literal> 内建函数</link>。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_string_for_string">
- <title>string (当被用作是字符串值时)</title>
-
- <para>什么也不做,仅仅返回和其内容一致的字符串。例外的是,
- 如果值是一个多类型的值(比如同时有字符串和序列两种),
- 那么结果就只是一个简单的字符串,而不是多类型的值。
- 这可以被用来防止人为多输入。</para>
- </section>
-
- <section xml:id="ref_builtin_substring">
- <title>substring (已废弃)</title>
-
- <indexterm>
- <primary>substring built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>substring</primary>
- </indexterm>
-
- <note>
- <para>从 FreeMarker 2.3.21 版本开始,该内建函数被废弃,由 <link
- linkend="dgui_template_exp_stringop_slice">字符串切分 </link> 替代,比如
- <literal><replaceable>str</replaceable>[<replaceable>from</replaceable>..<<replaceable>toExclusive</replaceable>]</literal>,
- <literal><replaceable>str</replaceable>[<replaceable>from</replaceable>..]</literal>,
- 和
- <literal><replaceable>str</replaceable>[<replaceable>from</replaceable>..*<replaceable>maxLength</replaceable>]</literal>。</para>
-
- <para>如果处理XML那么有一点警示:因为分割表达式作用于序列和字符串,
- 而且XML结点通常既是序列又是字符串,那么相等的表达式是
- <literal><replaceable>someXmlNode</replaceable>?string[<replaceable>from</replaceable>..<<replaceable>toExclusive</replaceable>]</literal>
- 和
- <literal><replaceable>exp</replaceable>?string[<replaceable>from</replaceable>..]</literal>,
- 因为没有 <literal>?string</literal> 它会分割结点序列而不是结点的字符串值。</para>
- </note>
-
- <note>
- <para>一些典型的字符串分割的用例已经由一些内建函数方便地实现: <link
- linkend="ref_builtin_remove_beginning"><literal>remove_beginning</literal></link>,
- <link
- linkend="ref_builtin_remove_ending"><literal>remove_ending</literal></link>,
- <link
- linkend="ref_builtin_keep_before"><literal>keep_before</literal></link>,
- <link
- linkend="ref_builtin_keep_after"><literal>keep_after</literal></link>,
- <link
- linkend="ref_builtin_keep_before_last"><literal>keep_before_last</literal></link>,
- <link
- linkend="ref_builtin_keep_after_last"><literal>keep_after_last</literal></link></para>
- </note>
-
- <para>概要:
- <literal><replaceable>exp</replaceable>?substring(<replaceable>from</replaceable>,
- <replaceable>toExclusive</replaceable>)</literal>,也可以使用
- <literal><replaceable>exp</replaceable>?substring(<replaceable>from</replaceable>)</literal>
- 调用。</para>
-
- <para>字符串的子串
- <literal><replaceable>from</replaceable></literal> 是第一个字符的索引。
- 它必须是数字,最小是0并且小于或等于
- <literal><replaceable>toExclusive</replaceable></literal>,
- 否则就会发生错误并且中止模板处理。
- <literal><replaceable>toExclusive</replaceable></literal>
- 是子串最后一个字符之后的字符位置索引,换句话说,它比最后一个字符的索引大1。
- 它必须是一个数字,最小是0并且小于或等于字符串的长度,
- 否则就会发生错误并且中止模板处理。如果
- <literal><replaceable>toExclusive</replaceable></literal> 被忽略了,
- 那么默认就是字符串的长度。如果参数是数字但不是整数,
- 那么只有数字中的整数部分会被使用。</para>
-
- <para>例如:</para>
-
- <programlisting role="template">- ${'abc'?substring(0)}
-- ${'abc'?substring(1)}
-- ${'abc'?substring(2)}
-- ${'abc'?substring(3)}
-
-- ${'abc'?substring(0, 0)}
-- ${'abc'?substring(0, 1)}
-- ${'abc'?substring(0, 2)}
-- ${'abc'?substring(0, 3)}
-
-- ${'abc'?substring(0, 1)}
-- ${'abc'?substring(1, 2)}
-- ${'abc'?substring(2, 3)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- abc
-- bc
-- c
--
-
--
-- a
-- ab
-- abc
-
-- a
-- b
-- c</programlisting>
- </section>
-
- <section xml:id="ref_builtin_trim">
- <title>trim</title>
-
- <indexterm>
- <primary>trim built-in</primary>
- </indexterm>
-
- <para>去掉字符串首尾的空格。例如:</para>
-
- <programlisting role="template">(${" green mouse "?trim})</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">(green mouse)</programlisting>
- </section>
-
- <section xml:id="ref_builtin_uncap_first">
- <title>uncap_first</title>
-
- <indexterm>
- <primary>uncap_first built-in</primary>
- </indexterm>
-
- <para>和 <link
- linkend="ref_builtin_cap_first"><literal>cap_first</literal></link> 相反。
- 字符串中所有单词的首字母小写。</para>
- </section>
-
- <section xml:id="ref_builtin_upper_case">
- <title>upper_case</title>
-
- <indexterm>
- <primary>upper_case built-in</primary>
- </indexterm>
-
- <para>字符串的大写形式。比如
- <literal>"GrEeN MoUsE"</literal> 将会是 <literal>"GREEN
- MOUSE"</literal>.</para>
- </section>
-
- <section xml:id="ref_builtin_url">
- <title>url</title>
-
- <indexterm>
- <primary>url built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>URL</secondary>
- </indexterm>
-
- <indexterm>
- <primary>encoding</primary>
-
- <secondary>URL</secondary>
- </indexterm>
-
- <indexterm>
- <primary>URL escaping</primary>
- </indexterm>
-
- <indexterm>
- <primary>URL encoding</primary>
- </indexterm>
-
- <indexterm>
- <primary>url_escaping_charset</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。
- 在2.3版本中是没有的。</para>
- </note>
-
- <para>在URL之后的字符串进行转义。这意味着,
- 所有非US-ASCII的字符和保留的URL字符将会被
- <literal>%<replaceable>XX</replaceable></literal> 形式转义。例如:</para>
-
- <programlisting role="template"><#assign x = 'a/b c'>
-${x?url}</programlisting>
-
- <para>将会输出(假设用来转义的字符集是US-ASCII兼容的字符集):</para>
-
- <programlisting role="output">a%2Fb%20c</programlisting>
-
- <para>请注意,它会转义 <emphasis>所有</emphasis> 保留的URL字符
- (<literal>/</literal>, <literal>=</literal>,
- <literal>&</literal>,等...),
- 所以编码可以被用来对查询参数的值进行,比如:</para>
-
- <programlisting role="template"><a href="foo.cgi?x=${x?url}&y=${y?url}">Click here...</a></programlisting>
-
- <note>
- <para>上面的没有HTML编码(<literal>?html</literal>)是需要的,
- 因为URL转义所有保留的HTML编码。但是要小心:通常引用的属性值,
- 用普通引号(<literal>"</literal>)包括,而不是单引号
- (<literal>'</literal>),因为单引号是不被URL转义的。</para>
- </note>
-
- <para>为了进行URL转义,必须要选择 <link
- linkend="gloss.charset">字符集</link>,它被用来计算被转义的部分
- (<literal>%<replaceable>XX</replaceable></literal>)。
- 如果你是HTML页面设计者,而且你不懂这个,不要担心:
- 程序员应该配置 FreeMarker,则它默认使用恰当的字符集
- (<phrase role="forProgrammers">程序员应该多看看下面的内容...</phrase>)。
- 如果你是一个比较热衷于技术的人,那么你也许想知道被
- <literal>url_escaping_charset</literal> 设置的指定字符集,
- 它可以在模板的执行时间设置(或者,更好的是,由程序员之前设置好)。例如:</para>
-
- <programlisting role="template"><#--
- This will use the charset specified by the programmers
- before the template execution has started.
--->
-<a href="foo.cgi?x=${x?url}">foo</a>
-
-<#-- Use UTF-8 charset for URL escaping from now: -->
-<emphasis><#setting url_escaping_charset="UTF-8"></emphasis>
-
-<#-- This will surely use UTF-8 charset -->
-<a href="bar.cgi?x=${x?url}">bar</a></programlisting>
-
- <para>此外,你可以明确地指定一个为单独URL转义的字符集,作为内建函数的参数:</para>
-
- <programlisting role="template"><a href="foo.cgi?x=${x?url<emphasis>('ISO-8895-2')</emphasis>}">foo</a></programlisting>
-
- <para><phrase role="forProgrammers"><indexterm>
- <primary>output encoding</primary>
- </indexterm><indexterm>
- <primary>output charset</primary>
- </indexterm>如果内建函数 <literal>url</literal> 没有参数,
- 那么它会使用由 <literal>url_escaping_charset</literal> 设置的字符集。
- 这个设置应该被软件设置,包括 FreeMarker(比如Web应用框架),
- 因为它默认不会被设置(<literal>null</literal>)。
- 如果它没有被设置,那么 FreeMarker 退回使用 <literal>output_encoding</literal>
- 的设置,这个也会被默认设置,所以它也是又软件设置的。
- 如果 <literal>output_encoding</literal> 也没有被设置,
- 那么没有参数的内建函数 <literal>url</literal> 将不会被执行,
- 而且它会引起运行时错误。当然,有参数的 <literal>url</literal> 函数将会执行。</phrase></para>
-
- <para><phrase role="forProgrammers">用 <literal>setting</literal>
- 指令在模板中设置 <literal>url_escaping_charset</literal> 是可能的。
- 至少在真实的MVC应用中,这是一个不好的实践行为。<literal>output_encoding</literal>
- 不能由 <literal>setting</literal> 指令来设置,所以它应该是软件的工作。
- 你可以阅读 <link linkend="pgui_misc_charset">这里...</link> 来获取更多信息。</phrase></para>
- </section>
-
- <section xml:id="ref_builtin_url_path">
- <title>url_path</title>
-
- <indexterm>
- <primary>url_path built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>URL path</secondary>
- </indexterm>
-
- <indexterm>
- <primary>encoding</primary>
-
- <secondary>URL path</secondary>
- </indexterm>
-
- <indexterm>
- <primary>URL escaping</primary>
- </indexterm>
-
- <indexterm>
- <primary>URL encoding</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始可用。</para>
- </note>
-
- <para>它和 <link linkend="ref_builtin_url">
- <literal>url</literal> 内建函数</link> 相同,只是它不转义斜杠
- (<literal>/</literal>)字符。这就是意味着用来转义使用了斜杠(不是反斜杠!)的路径
- (比如操作系统或一些内容仓库的路径),转义之后它们可以插入到URL中。
- 需要该转义的常用原因是文件夹名称或文件名称可能含有非US-ASCII字母
- (<quote>国家</quote> 标准符号)。</para>
-
- <note>
- <para>和 <link linkend="ref_builtin_url">the
- <literal>url</literal> 内建函数</link> 相似,希望的URL转义字符
- (或者后退,输出编码)必须要在 FreeMarker的配置设置项中设置,
- 否则内建函数就会报错。或者可以指定字符集,比如
- <literal>somePath?url_path('utf-8')</literal>。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_word_list">
- <title>word_list</title>
-
- <indexterm>
- <primary>word_list built-in</primary>
- </indexterm>
-
- <para>包含字符串中所有单词的序列,顺序为出现在字符串中的顺序。
- 单词是不间断的字符序列,包含了任意字符,但是没有 <link
- linkend="gloss.whiteSpace">空白</link>。例如:</para>
-
- <programlisting role="template"><#assign words = " a bcd, . 1-2-3"?word_list>
-<#list words as word>[${word}]</#list>
-</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">[a][bcd,][.][1-2-3]</programlisting>
- </section>
-
- <section xml:id="ref_builtin_xhtml">
- <title>xhtml</title>
-
- <indexterm>
- <primary>xhtml built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>output</secondary>
- </indexterm>
-
- <para>字符串作为XHTML格式文本,下面这些:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><</literal> 替换为
- <literal>&lt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>></literal> 替换为
- <literal>&gt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>&</literal> 替换为
- <literal>&amp;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"</literal> 替换为
- <literal>&quot;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>'</literal> 替换为
- <literal>&#39;</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>该内建函数和 <literal>xml</literal> 内建函数的唯一不同是
- <literal>xhtml</literal>内建函数转义 <literal>'</literal>
- 为 <literal>&#39;</literal>,而不是 <literal>&apos;</literal>,
- 因为一些老版本的浏览器不能正确解释 <literal>&apos;</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_xml">
- <title>xml</title>
-
- <indexterm>
- <primary>xml built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>escaping</primary>
-
- <secondary>output</secondary>
- </indexterm>
-
- <para>字符串作为XML格式文本,下面这些:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><</literal> 替换为
- <literal>&lt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>></literal> 替换为
- <literal>&gt;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>&</literal> 替换为
- <literal>&amp;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"</literal> 替换为
- <literal>&quot;</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>'</literal> 替换为
- <literal>&apos;</literal></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_builtin_string_flags">
- <title>通用标志</title>
-
- <para>很多字符串内建函数接受可选的字符串参数,
- 它们被称为"标志"。在这个字符串中,每个字母影响内建函数一个特定方面的行为。
- 比如,字母 <literal>i</literal> 说明内建函数不应该区别相同字母的小写和大写变化。
- 标志字符串中的字母顺序是不重要的。</para>
-
- <para>下面是字母(标志)的完整列表:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>i</literal>:大小写敏感:
- 不区别相同字母的小写和大写变化。</para>
- </listitem>
-
- <listitem>
- <para><literal>f</literal>:只是第一个。也就是说,
- 替换/查找/等...,只是第一个出现的地方。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>regular expression</primary>
-
- <secondary>built-ins</secondary>
- </indexterm> <literal>r</literal>:查找的子串是
- <link linkend="gloss.regularExpression">正则表达式</link>。
- FreeMarker 使用的正则表达式变量可以在 <link
- xlink:href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html</link>
- 中找到(请注意,一些模式规则特性的出现基于所使用的Java版本)。</para>
- </listitem>
-
- <listitem>
- <para><literal>m</literal>:正则表达式的多行模式。在多行正则表达式
- <literal>^</literal> 和 <literal>$</literal> 仅仅匹配之后或之前,
- 各自匹配行终止符或字符串结尾。默认情况下,这些表达式仅仅匹配完整字符串的开头和结尾。
- 请注意,<literal>^</literal> 和 <literal>$</literal> 不匹配换行符字符本身。</para>
- </listitem>
-
- <listitem>
- <para><literal>s</literal>:开启正则表达式的dot-all模式
- (和Perl的单行模式相同)。在dot-all模式下,表达式
- <literal>.</literal> 匹配任意字符串,包含行终止符。
- 默认情况下,该表达式不匹配行终止符。</para>
- </listitem>
-
- <listitem>
- <para><literal>c</literal>: 允许正则表达式中的空白和注释。</para>
- </listitem>
- </itemizedlist>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign s = 'foo bAr baar'>
-${s?replace('ba', 'XY')}
-i: ${s?replace('ba', 'XY', 'i')}
-if: ${s?replace('ba', 'XY', 'if')}
-r: ${s?replace('ba*', 'XY', 'r')}
-ri: ${s?replace('ba*', 'XY', 'ri')}
-rif: ${s?replace('ba*', 'XY', 'rif')}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">foo bAr XYar
-i: foo XYr XYar
-if: foo XYr baar
-r: foo XYAr XYr
-ri: foo XYr XYr
-rif: foo XYr baar</programlisting>
-
- <para>下表是支持使用这些通用标志的内建函数:</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>内建函数</th>
-
- <th><literal>i</literal> (忽略大小写)</th>
-
- <th><literal>r</literal> (正则表达式)</th>
-
- <th><literal>m</literal> (多行模式)</th>
-
- <th><literal>s</literal> (dot-all模式)</th>
-
- <th><literal>c</literal> (空白和注释)</th>
-
- <th><literal>f</literal> (仅第一个)</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>replace</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>是</td>
- </tr>
-
- <tr>
- <td><literal>split</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>仅 <literal>r</literal></td>
-
- <td>否</td>
- </tr>
-
- <tr>
- <td><literal>matches</literal></td>
-
- <td>是</td>
-
- <td>忽略</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>否</td>
- </tr>
-
- <tr>
- <td><literal>keep_after</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>忽略</td>
- </tr>
-
- <tr>
- <td><literal>keep_after_last</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>忽略</td>
- </tr>
-
- <tr>
- <td><literal>keep_before</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>忽略</td>
- </tr>
-
- <tr>
- <td><literal>keep_before_last</literal></td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>忽略</td>
- </tr>
-
- <tr>
- <td><literal>ensure_starts_with</literal></td>
-
- <td>是</td>
-
- <td>忽略</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>是</td>
-
- <td>忽略</td>
- </tr>
- </tbody>
- </informaltable>
- </section>
- </section>
-
- <section xml:id="ref_builtins_number">
- <title>数字内建函数</title>
-
- <indexterm>
- <primary>number</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <para>相关的FAQ:如果有和 1,000,000 或 1 000 000 而不是 1000000 类似的东西,
- 或者是 3.14 而不是 3,14 的东西,反之亦然,请参考<link
- linkend="faq_number_grouping">这里</link> 和 <link
- linkend="faq_number_decimal_point">这里</link> FAQ中相关内容,
- 也要注意上述内容中的内建函数 <literal>c</literal>。</para>
-
- <section xml:id="ref_builtin_abs">
- <title>abs</title>
-
- <indexterm>
- <primary>abs built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>absolute value</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.20 版本开始存在。</para>
- </note>
-
- <para>给出数字的绝对值。比如
- <literal>x?abs</literal> ,如果 <literal>x</literal> 是 -5,会得到5。</para>
- </section>
-
- <section xml:id="ref_builtin_c">
- <title>c (当被用作是数字值时)</title>
-
- <indexterm>
- <primary>c built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>number</secondary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.3 版本开始存在。</para>
- </note>
-
- <para>该内建函数将 <quote>计算机语言</quote> 的数字转换成字符串,
- 这都是对计算机来说的,而不是对用户。也就是说,
- 它根据程序语言的用法来进行格式化,
- 这对于 FreeMarker 的所有本地数字格式化设置来说是独立的。
- 它通常使用点来作为小数分隔符,而且它从来不用分组分隔符(像3,000,000),
- 指数形式(比如5E20),多余的在开头或结尾的0(比如03或1.0),还有+号(比如+1)。
- 它最多在小数点后打印16位,因此数值的绝对值小于1E-16将会显示为0。
- 该内建函数非常严格,因为作为默认(像 <literal>${x}</literal> 这样)
- 数字被本地(语言,国家)特定的数字格式转换为字符串,这是让用户来看的
- (比如3000000可能会被打印为3,000,000)。当数字不对用户打印时
- (比如,对于一个数据库记录ID,用作是URL的一部分,或者是HTML表单中的隐藏域,
- 或者打印CSS/JavaScript的数值文本),该内建函数必须被用来打印数字
- (也就是使用 <literal>${x?c}</literal> 来代替 <literal>${x}</literal>),
- 否则输出可能由于当前数字格式设置,本地化
- (比如在一些国家中,小数点不是点,而是逗号)和数值
- (像大数可能被分组分隔符"损坏")而损坏。</para>
-
- <para>如果 FreeMarker 配置设置项 <literal>incompatible_imporvements</literal>
- 为 2.3.21 (或更高),该内建函数将会对正无穷/负无穷,IEEE非浮点数各自返回
- <literal>"INF"</literal>, <literal>"-INF"</literal> 和
- <literal>"NaN"</literal>。这是这些特殊值的XML Schema兼容性表现。
- (之前它会返回US本地化下的 <literal>java.text.DecimalFormat</literal>,
- 不被任意(通用)计算机语言所理解。)</para>
-
- <para>请注意,该内建函数 <link
- linkend="ref_builtin_c_boolean">也对布尔值起作用</link>。</para>
- </section>
-
- <section xml:id="ref_builtin_is_infinite">
- <title>is_infinite</title>
-
- <indexterm>
- <primary>is_infinte built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>infinite</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.20 版本开始存在。</para>
- </note>
-
- <para>辨别数字是否是无限浮点数(根据IEEE 754)。比如,
- 基于 <literal>someNumber</literal> 的值是否是无限,
- <literal>someNumber?is_infinite</literal> 结果是
- <literal>true</literal> 或 <literal>false</literal>,当然,
- 如果该数不是浮点类型,那么将会返回 <literal>false</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_is_nan">
- <title>is_nan</title>
-
- <indexterm>
- <primary>is_nan built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>NaN</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.20 版本开始存在。</para>
- </note>
-
- <para>辨别数字是否是浮点数NaN(根据IEEE 754)。比如,
- 基于 <literal>someNumber</literal> 的值是否是 NaN,
- <literal>someNumber?is_nan</literal> 结果是
- <literal>true</literal> 或 <literal>false</literal>,当然,
- 如果该数不是浮点类型,那么将会返回 <literal>false</literal>。</para>
- </section>
-
- <section xml:id="ref_builtin_lower_abc">
- <title>lower_abc</title>
-
- <indexterm>
- <primary>lower_abc built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>ABC</primary>
- </indexterm>
-
- <indexterm>
- <primary>alphabetical ordinals</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.22 版本开始存在。</para>
- </note>
-
- <para>将 <literal>1</literal>, <literal>2</literal>,
- <literal>3</literal>,等...,转换为字符串 <literal>"a"</literal>,
- <literal>"b"</literal>, <literal>"c"</literal>,等... 当到达
- <literal>"z"</literal>时,那么会继续转换成如 <literal>"aa"</literal>,
- <literal>"ab"</literal>,等... 这和电子表格应用程序(比如Excel或Calc)
- 的列标签有着相同的逻辑。数字的最小值是 <literal>1</literal>。
- 没有上限值。如果数字是 <literal>0</literal> 或更小或它不是整数,
- 那么模板处理将会中止并发生错误。</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><#list 1..30 as n>${n?lower_abc} </#list></programlisting>
-
- <para>输出:</para>
-
- <programlisting role="output">a b c d e f g h i j k l m n o p q r s t u v w x y z aa ab ac ad </programlisting>
-
- <para>请参考: <link
- linkend="ref_builtin_upper_abc"><literal>upper_abc</literal></link></para>
- </section>
-
- <section xml:id="ref_builtin_rounding">
- <title>round, floor, ceiling</title>
-
- <indexterm>
- <primary>rounding</primary>
- </indexterm>
-
- <indexterm>
- <primary>floor built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>ceiling built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>round built-in</primary>
- </indexterm>
-
- <note>
- <para>内建函数 round 从 FreeMarker 2.3.13 版本开始存在。</para>
- </note>
-
- <para>使用确定的舍入法则,转换一个数字到整数:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>round</literal>:返回最近的整数。
- 如果数字以.5结尾,那么它将进位(也就是向正无穷方向进位)</para>
- </listitem>
-
- <listitem>
- <para><literal>floor</literal>:返回数字的舍掉小数后的整数
- (也就是向负无穷舍弃)</para>
- </listitem>
-
- <listitem>
- <para><literal>ceiling</literal>:返回数字小数进位后的整数
- (也就是向正无穷进位)</para>
- </listitem>
- </itemizedlist>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign testlist=[
- 0, 1, -1, 0.5, 1.5, -0.5,
- -1.5, 0.25, -0.25, 1.75, -1.75]>
-<#list testlist as result>
- ${result} ?floor=${result?floor} ?ceiling=${result?ceiling} ?round=${result?round}
-</#list></programlisting>
-
- <para>输出:</para>
-
- <programlisting role="output"> 0 ?floor=0 ?ceiling=0 ?round=0
- 1 ?floor=1 ?ceiling=1 ?round=1
- -1 ?floor=-1 ?ceiling=-1 ?round=-1
- 0.5 ?floor=0 ?ceiling=1 ?round=1
- 1.5 ?floor=1 ?ceiling=2 ?round=2
- -0.5 ?floor=-1 ?ceiling=0 ?round=0
- -1.5 ?floor=-2 ?ceiling=-1 ?round=-1
- 0.25 ?floor=0 ?ceiling=1 ?round=0
- -0.25 ?floor=-1 ?ceiling=0 ?round=0
- 1.75 ?floor=1 ?ceiling=2 ?round=2
- -1.75 ?floor=-2 ?ceiling=-1 ?round=-2</programlisting>
-
- <para>这些内建函数在分页处理时也许有用。如果你仅仅想
- <emphasis>展示</emphasis> 数字的舍入形式,那么应该使用 <link
- linkend="ref_builtin_string_for_number"><literal>string</literal>
- 内建函数</link> 或者 <link
- linkend="ref.setting.number_format"><literal>number_format</literal>
- 设置</link>。</para>
- </section>
-
- <section xml:id="ref_builtin_string_for_number">
- <title>string (当用作是数字类型时)</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>string built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>number</secondary>
- </indexterm>
-
- <para>将一个数字转换成字符串。它使用程序员通过
- <literal>number_format</literal> 和 <literal>locale</literal>
- 设置的默认格式。也可以明确地用这个内建函数再指定一个数字格式,
- 这在后面会展示。</para>
-
- <para>有四种预定义的数字格式:<literal>computer</literal>,
- <literal>currency</literal>,<literal>number</literal> 和
- <literal>percent</literal>。这些格式的明确含义是本地化(国家)指定的,
- 受Java平台安装环境所控制,而不是FreeMarker,除了
- <literal>computer</literal>,用作和 <link linkend="ref_builtin_c"> <literal>c</literal>
- 内建函数</link>是相同的格式。可以这样来使用预定义的格式:</para>
-
- <programlisting role="template"><#assign x=42>
-${x}
-${x?string} <#-- the same as ${x} -->
-${x?string.number}
-${x?string.currency}
-${x?string.percent}
-${x?string.computer}</programlisting>
-
- <para>如果你本地是US English,将会输出:</para>
-
- <programlisting role="output">42
-42
-42
-$42.00
-4,200%
-42</programlisting>
-
- <para>前三个表达式的输出是相同的,因为前两个表达式是默认格式,
- 这里是"数字"。可以使用一个设置来改变默认设置:</para>
-
- <programlisting role="template"><#setting number_format="currency">
-<#assign x=42>
-${x}
-${x?string} <#-- the same as ${x} -->
-${x?string.number}
-${x?string.currency}
-${x?string.percent}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">$42.00
-$42.00
-42
-$42.00
-4,200%</programlisting>
-
- <para>因为默认的数字格式被设置成了"货币"。</para>
-
- <para>除了这三种预定义格式,还可以使用 <link
- xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/text/DecimalFormat.html">Java
- 数字格式语法</link> 中的任意数字格式:</para>
-
- <programlisting role="template"><#assign x = 1.234>
-${x?string["0"]}
-${x?string["0.#"]}
-${x?string["0.##"]}
-${x?string["0.###"]}
-${x?string["0.####"]}
-
-${1?string["000.00"]}
-${12.1?string["000.00"]}
-${123.456?string["000.00"]}
-
-${1.2?string["0"]}
-${1.8?string["0"]}
-${1.5?string["0"]} <-- 1.5, rounded towards even neighbor
-${2.5?string["0"]} <-- 2.5, rounded towards even neighbor
-
-${12345?string["0.##E0"]}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">1
-1.2
-1.23
-1.234
-1.234
-
-001.00
-012.10
-123.46
-
-1
-2
-2 <-- 1.5, rounded towards even neighbor
-2 <-- 2.5, rounded towards even neighbor
-
-1.23E4</programlisting>
-
- <para>请注意,在 FreeMarker 中,<literal>foo.bar</literal> 和
- <literal>foo["bar"]</literal> 是相同的,也可以将
- <literal>x?string.currency</literal> 写为
- <literal>x?string["currency"]</literal>,当然实际中不这么用。
- 但是在上述示例中,我们不得不使用方括号语法,因为在语法上,
- 使用的字符(数字,点,<literal>#</literal>)不允许在点操作符之后。</para>
-
- <para>由于历史原因,也可以编写如下代码
- <literal>x?string("0.#")</literal>,它和
- <literal>x?string["0.#"]</literal> 完全相同。</para>
-
- <para>在金融和统计学实践中,四舍五入都是根据所谓的一半原则,
- 这就意味着对最近的"邻居"进行四舍五入,除非离两个邻居距离相等,
- 这种情况下,它四舍五入到偶数的邻居。如果你注意看1.5和2.5的四舍五入的话,
- 这在上面的示例中是可以看到的,两个都被四舍五入到2,因为2是偶数,但1和3是奇数。</para>
-
- <para>正如之前展示的预定义格式,数字的默认格式可以在模板中设置:</para>
-
- <programlisting role="template"><#setting number_format="0.##">
-${1.234}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">1.23</programlisting>
-
- <para>请注意,数字格式是本地化敏感的,本地化设置在格式化中起作用:</para>
-
- <programlisting role="template"><#setting number_format=",##0.00">
-<#setting locale="en_US">
-US people write: ${12345678}
-<#setting locale="hu">
-German people write: ${12345678}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">US people write: 12,345,678.00
-German people write: 12.345.678,00</programlisting>
- </section>
-
- <section xml:id="ref_builtin_upper_abc">
- <title>upper_abc</title>
-
- <indexterm>
- <primary>upper_abc built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>ABC</primary>
- </indexterm>
-
- <indexterm>
- <primary>alphabetical ordinals</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.22 版本开始存在。</para>
- </note>
-
- <para>和 <link
- linkend="ref_builtin_lower_abc"><literal>lower_abc</literal></link> 相同,
- 但是它转换成大写字母,比如 <literal>"A"</literal>,
- <literal>"B"</literal>, <literal>"C"</literal>,… ,
- <literal>"AA"</literal>, <literal>"AB"</literal>, 等...</para>
- </section>
- </section>
-
- <section xml:id="ref_builtins_date">
- <title>日期内建函数</title>
-
- <indexterm>
- <primary>date</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <indexterm>
- <primary>time</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <indexterm>
- <primary>datetime</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <indexterm>
- <primary>timestamp</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <section xml:id="ref_builtin_date_datetype">
- <title>date, time, datetime (当用于日期/时间/日期-时间值时)</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>date built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>time built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>datetime built-in</primary>
- </indexterm>
-
- <para>这些内建函数用来指定日期变量中的哪些部分被使用:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>date</literal>:仅日期部分,没有一天当中的时间部分。</para>
- </listitem>
-
- <listitem>
- <para><literal>time</literal>:仅一天当中的时间部分,没有日期部分。</para>
- </listitem>
-
- <listitem>
- <para><literal>datetime</literal>:日期和时间都在</para>
- </listitem>
- </itemizedlist>
-
- <para>在最佳情况下,你不需要使用这些内建函数。不幸的是,
- 由于Java平台上的技术限制,FreeMarker 有时不能发现日期中的哪一部分在使用;
- 询问程序员哪些变量会有这个问题。如果 FreeMarker 不得不执行需要这些信息的操作
- --比如用文本显示日期--但是它不知道哪一部分在使用,它会以错误来中止运行。
- 这就是你不得不使用这些内建函数的时候了。比如,假设
- <literal>openingTime</literal> 是一个有这样问题的变量:</para>
-
- <programlisting role="template"><#assign x = openingTime> <#-- no problem can occur here -->
-${openingTime?time} <#-- without ?time it would fail -->
-<#-- For the sake of better understanding, consider this: -->
-<#assign openingTime = openingTime?time>
-${openingTime} <#-- this will work now --></programlisting>
-
- <para>这些内建函数也可以用来将日期-时间值转换成日期或时间。例如:</para>
-
- <programlisting role="template">Last updated: ${lastUpdated} <#-- assume that lastUpdated is a date-time value -->
-Last updated date: ${lastUpdated?date}
-Last updated time: ${lastUpdated?time}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Last updated: 04/25/2003 08:00:54 PM
-Last updated date: 04/25/2003
-Last updated time: 08:00:54 PM</programlisting>
-
- <para>如果 <literal>?</literal> 左边是字符串,那么这些内建函数
- <link linkend="ref_builtin_string_date">将字符串转换成日期/时间/日期时间</link>。</para>
- </section>
-
- <section xml:id="ref_builtin_date_if_unknown">
- <title>date_if_unknown, time_if_unknown, datetime_if_unknown</title>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.21 版本开始存在</para>
- </note>
-
- <para><literal>date_if_unknown</literal>,
- <literal>time_if_unknown</literal>,
- <literal>datetime_if_unknown</literal>
- 内建函数使用一些子类型来标记日期类型的值:日期没有时间,时间,或日期-时间。
- 如果变量值已经持有这些信息,那么内建函数就不会起作用。也就是说,
- 它不会转换变量值的子类型,如果它是未知的,则会添加子类型。</para>
- </section>
-
- <section xml:id="ref_builtin_date_iso">
- <title>iso_...</title>
-
- <indexterm>
- <primary>iso built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>iso_... built-ins</primary>
- </indexterm>
-
- <note>
- <para><emphasis>这些内建函数从 FreeMarker 2.3.21 版本开始废弃</emphasis>,
- 因为 <literal>date_format</literal>,<literal>time_format</literal> 和
- <literal>datetime_format</literal> 设置理解
- <literal>"iso"</literal> (ISO 8601:2004 格式) 和
- <literal>"xs"</literal> (XML Schema 格式),此外还有
- Java <literal>SimpleDateFormat</literal> 格式。因此默认格式可以设置为ISO 8601,
- 要使用一次ISO格式,可以使用 <literal>myDate?string.iso</literal>。 <link
- linkend="topic.dateTimeFormatSettings">更多内容请参考...</link></para>
- </note>
-
- <para>这些内建函数转换日期,时间或日期-时间值为字符串,遵循 ISO 8601:2004 "扩展" 格式。</para>
-
- <para>该内建函数有很多表现形式:
- <literal>iso_utc</literal>, <literal>iso_local</literal>,
- <literal>iso_utc_nz</literal>, <literal>iso_local_nz</literal>,
- <literal>iso_utc_m</literal>, <literal>iso_utc_m_nz</literal>,等。
- 名称的构成由下列单词顺序组成,每部分由一个 <literal>_</literal> 分隔开:</para>
-
- <orderedlist>
- <listitem>
- <para><literal>iso</literal> (必须的)</para>
- </listitem>
-
- <listitem>
- <para>是 <literal>utc</literal> 或 <literal>local</literal> 二者之一
- (必须的(除了给定一个参数,这个后面再来说)):
- 来指定根据UTC或根据当前时区来打印日期/时间/日期-时间。
- 当前时区是根据 FreeMarker 的设置项 <literal>time_zone</literal>
- 来确定的,它通常是由程序员在模板外配置的(当然它也可以在模板内设置,
- 比如使用<literal><#setting time_zone="America/New_York"></literal>)。
- 请注意,如果 FreeMarker 设置项 <literal>sql_date_and_time_time_zone</literal>
- 已经设置并且非 <literal>null</literal>,那么对于 <literal>java.sql.Date</literal>
- 和 <literal>java.sql.Time</literal> 值(也就是通过SQL从数据中获得的仅日期值和仅时间值)
- <literal>local</literal> 的时区值将会替代 <literal>time_zone</literal> 设置的值。</para>
- </listitem>
-
- <listitem>
- <para><literal>h</literal>,<literal>m</literal> 或
- <literal>ms</literal>(可选的):时间部分的精度。
- 当忽略的时候,就默认设置到秒的精度(比如<literal>12:30:18</literal>)。
- <literal>h</literal> 表示小时的精度(比如 <literal>12</literal>),
- <literal>m</literal> 表示分钟的精度(比如 <literal>12:30</literal>),
- <literal>ms</literal> 就表示毫秒的精度(<literal>12:30:18.25</literal>,这里表示250毫秒)。
- 要注意当使用 <literal>ms</literal> 时,毫秒会显示为百分制的形式
- (遵循标准)而且不会去尾到 <literal>0</literal> 秒。因此,
- 如果毫秒的部分变成 <literal>0</literal> 的话,整个的毫秒的部分就会被忽略掉了。
- 同时也要注意毫秒的部分是由一个点来分隔的,而不是逗号
- (遵循Web约定和XML Schema的日期/时间格式)。</para>
- </listitem>
-
- <listitem>
- <para><literal>nz</literal> (可选的): <literal>nz</literal>
- (比如在 <literal>${foo?utc_local_nz}</literal> 中) 代表
- <quote>没有时区</quote>,也就意味着时区的偏移量
- (比如 <literal>+02:00</literal> 或者 <literal>-04:30</literal>
- 或者 <literal>Z</literal>)不会显示出来。如果这部分被忽略了
- (比如在 <literal>${foo?utc_local}</literal> 中) 那么时区就会显示出来,
- 除了两种情况:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果值是日期(没有时间部分)值(再说一次,ISO 8901 标准不允许)</para>
- </listitem>
-
- <listitem>
- <para>如果值是 <literal>java.sql.Time</literal> 而且 FreeMarker 配置设置项
- <literal>incompatible_improvements</literal> (通常通过
- Java代码 <literal>Configuration</literal> 的构造方法参数设置) 最小是2.3.21。
- 存储时间值是不分时区的,只是存储小时,分钟,秒和小数秒值,
- 所以显示时区就没什么意义。</para>
- </listitem>
- </itemizedlist>
-
- <para>请注意,从 FreeMarker 2.3.19 版本开始,对于XML Schema
- 日期/时间/日期时间格式的兼容性,时区偏移量通常包含分钟。
- (如果主要生成XML Schema格式,就使用 xs 格式。)</para>
- </listitem>
- </orderedlist>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign aDateTime = .now>
-<#assign aDate = aDateTime?date>
-<#assign aTime = aDateTime?time>
-
-Basic formats:
-${aDate?iso_utc}
-${aTime?iso_utc}
-${aDateTime?iso_utc}
-
-Different accuracies:
-${aTime?iso_utc_ms}
-${aDateTime?iso_utc_m}
-
-Local time zone:
-${aDateTime?iso_local}</programlisting>
-
- <para>可能的输出(基于当前时间和时区):</para>
-
- <programlisting role="output">Basic formats:
-2011-05-16
-21:32:13Z
-2011-05-16T21:32:13Z
-
-Different accuracies:
-21:32:13.868Z
-2011-05-16T21:32Z
-
-Local time zone:
-2011-05-16T23:32:13+02:00</programlisting>
-
- <para>还有另外一组 <literal>iso_...</literal> 内建函数形式,
- 你可从名称中以忽略掉 <literal>local</literal> 或 <literal>utc</literal>,
- 但是要指定时区作为内建函数的参数。例如:</para>
-
- <programlisting role="template"><#assign aDateTime = .now>
-${aDateTime?iso("UTC")}
-${aDateTime?iso("GMT-02:30")}
-${aDateTime?iso("Europe/Rome")}
-
-The usual variations are supported:
-${aDateTime?iso_m("GMT+02")}
-${aDateTime?iso_m_nz("GMT+02")}
-${aDateTime?iso_nz("GMT+02")}</programlisting>
-
- <para>可能的输出(基于当前时间和时区):</para>
-
- <programlisting role="output">2011-05-16T21:43:58Z
-2011-05-16T19:13:58-02:30
-2011-05-16T23:43:58+02:00
-
-The usual variations are supported:
-2011-05-16T23:43+02:00
-2011-05-16T23:43
-2011-05-16T23:43:58</programlisting>
-
- <para>如果时区参数不能被解释,模板处理就会终止并发生错误。</para>
-
- <para role="forProgrammers">参数可以是
- <literal>java.util.TimeZone</literal> 对象(可能是Java方法的返回值,
- 或者在数据模型中),而不能只是字符串。</para>
- </section>
-
- <section xml:id="ref_builtin_string_for_date">
- <title>string (当用于日期/时间/日期-时间值时)</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>string built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>date</secondary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>time</secondary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>date-time</secondary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>timestamp</secondary>
- </indexterm>
-
- <indexterm>
- <primary>converting date to string</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting time to string</primary>
- </indexterm>
-
- <indexterm>
- <primary>time to string</primary>
- </indexterm>
-
- <indexterm>
- <primary>date to string</primary>
- </indexterm>
-
- <para>这个内建函数以指定的格式转换日期类型到字符串类型。</para>
-
- <note>
- <para>应该很少使用这个内建函数,因为日期/时间/日期-时间值的默认格式可以全局指定
- FreeMarker 的 <link
- linkend="topic.dateTimeFormatSettings">
- <literal>date_format</literal>,<literal>time_format</literal> 和
- <literal>datetime_format</literal> 设置</link>。
- 该内建函数只在期望格式和常用格式不同的地方使用。在其它地方,
- 默认格式应该由程序员在模板之外合理地设置。</para>
- </note>
-
- <para>期望的格式可以由
- <literal>?string.<replaceable>format</replaceable></literal> 或
- <literal>?string["<replaceable>format</replaceable>"]</literal>
- (或历史上等同的,
- <literal>?string("<replaceable>format</replaceable>")</literal>)
- 来指定。这些都是等同的,除了使用引号格式的,它可以在
- <literal><replaceable>format</replaceable></literal> 中包含任意字符,
- 比如空格。<literal><replaceable>format</replaceable></literal> 的语法和配置设置项
- <literal>date_format</literal>,<literal>time_format</literal> 和
- <literal>datetime_format</literal> 是一样的; <link
- linkend="topic.dateTimeFormatSettings">请参考这些可能值的文档</link>。</para>
-
- <para>例如:如果输出的本地化设置是美国英语,时区是美国太平洋时区,并且
- <literal>openingTime</literal> 是
- <literal>java.sql.Time</literal>, <literal>nextDiscountDay</literal>
- 是 <literal>java.sql.Date</literal> 而
- <literal>lastUpdated</literal> 是
- <literal>java.sql.Timestamp</literal> 或
- <literal>java.util.Date</literal> 那么:</para>
-
- <programlisting role="template">${openingTime?string.short}
-${openingTime?string.medium}
-${openingTime?string.long}
-${openingTime?string.full}
-${openingTime?string.xs}
-${openingTime?string.iso}
-
-${nextDiscountDay?string.short}
-${nextDiscountDay?string.medium}
-${nextDiscountDay?string.long}
-${nextDiscountDay?string.full}
-${nextDiscountDay?string.xs}
-${nextDiscountDay?string.iso}
-
-${lastUpdated?string.short}
-${lastUpdated?string.medium}
-${lastUpdated?string.long}
-${lastUpdated?string.full}
-${lastUpdated?string.medium_short} <#-- medium date, short time -->
-${lastUpdated?string.xs}
-${lastUpdated?string.iso}
-
-<#-- SimpleDateFormat patterns: -->
-${lastUpdated?string["dd.MM.yyyy, HH:mm"]}
-${lastUpdated?string["EEEE, MMMM dd, yyyy, hh:mm a '('zzz')'"]}
-${lastUpdated?string["EEE, MMM d, ''yy"]}
-${lastUpdated?string.yyyy} <#-- Same as ${lastUpdated?string["yyyy"]} -->
-
-<#-- Advanced ISO 8601-related formats: -->
-${lastUpdated?string.iso_m_u}
-${lastUpdated?string.xs_ms_nz}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">01:45 PM
-01:45:09 PM
-01:45:09 PM PST
-01:45:09 PM PST
-13:45:09-08:00
-13:45:09-08:00
-
-2/20/07
-Apr 20, 2007
-April 20, 2007
-Friday, April 20, 2007
-2007-02-20-08:00
-2007-02-20
-
-2/20/07 01:45 PM
-Feb 20, 2007 01:45:09 PM
-February 20, 2007 01:45:09 PM PST
-Friday, February 20, 2007 01:45:09 PM PST
-Feb 8, 2003 9:24 PM
-2007-02-20T13:45:09-08:00
-2007-02-20T13:45:09-08:00
-
-08.04.2003 21:24
-Tuesday, April 08, 2003, 09:24 PM (PDT)
-Tue, Apr 8, '03
-2003
-
-2007-02-20T21:45Z
-2007-02-20T13:45:09.000</programlisting>
-
- <warning>
- <para>不幸的是,由于Java平台的限制,在数据模型中可以有日期变量,
- 那里 FreeMarker 不能决定变量是否是日期(年,月,日),还是时间
- (时,分,秒,毫秒),或者是日期-时间值。这种情况下,当你编写如
- <literal>${lastUpdated?string.short}</literal> 或
- <literal>${lastUpdated?string.xs}</literal> 时,FreeMarker
- 不知道如何来显示日期,也就是说,格式不指定精确的字段去显示,
- 或者如果只是简单使用 <literal>${lastUpdated}</literal>,
- 那么模板会中止执行并抛出错误。要阻止这些发生,可以使用 <link
- linkend="ref_builtin_date_datetype"><literal>?date</literal>,
- <literal>?time</literal> 和 <literal>?datetime</literal>
- 内建函数</link> 来帮助 FreeMarker。例如:
- <literal>${lastUpdated?datetime?string.short}</literal>。
- 要询问程序员数据模型中确定的变量是否有这个问题,
- 或通常使用内建函数 <literal>?date</literal>,<literal>?time</literal>
- 和 <literal>?datetime</literal> 来安全处理。</para>
- </warning>
-
- <note>
- <para>不需和格式模式使用 <literal>?date</literal>,
- <literal>?time</literal> 或 <literal>?datetime</literal>,
- 比如 <literal>"yyyy.MM.dd HH:mm"</literal>,因为使用这些模式,
- 就告诉 FreeMarker 来显示哪部分日期。那么,FreeMarker 将盲目地相信你,
- 如果你显示的部分不存在于变量中,则可以显示"干扰"。例如,
- <literal>${openingTime?string["yyyy-MM-dd hh:mm:ss a"]}</literal>,
- 而 <literal>openingTime</literal> 中只存储了时间,将会显示
- <literal>1970-01-01 09:24:44 PM</literal>。</para>
- </note>
-
- <para>要避免误解,需要的格式不是字符串,它可以是变量或任意表达式,比如
- <literal>"<replaceable>...</replaceable>"?string[myFormat]</literal>。</para>
-
- <para>请参考: <link
- linkend="dgui_template_valueinserion_universal_date">日期的插入</link></para>
- </section>
- </section>
-
- <section xml:id="ref_builtins_boolean">
- <title>布尔值内建函数</title>
-
- <indexterm>
- <primary>boolean</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <section xml:id="ref_builtin_c_boolean">
- <title>c (当用于布尔值时)</title>
-
- <indexterm>
- <primary>c built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>boolean</secondary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.20 版本开始存在</para>
- </note>
-
- <para>该内建函数将布尔值转换为字符串,针对
- <quote>计算机语言</quote> 而不是用户。不管
- <literal>boolean_format</literal> 的设置是什么,
- 结果是 <literal>"true"</literal> 或 <literal>"false"</literal>。
- 当生成JavaScript的时候,应该会用到它,否则修改
- <literal>boolean_format</literal> 的话可以打断生成的计算机语言输出。</para>
-
- <para>请注意,该内建函数 <link linkend="ref_builtin_c">对字符串也起作用</link>。</para>
- </section>
-
- <section xml:id="ref_builtin_string_for_boolean">
- <title>string (当用于布尔值时)</title>
-
- <indexterm>
- <primary>boolean</primary>
-
- <secondary>printing</secondary>
- </indexterm>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>string built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>format</primary>
-
- <secondary>boolean</secondary>
- </indexterm>
-
- <para><emphasis>该内建函数的所有用法都已经废弃了;请继续阅读下面内容。</emphasis></para>
-
- <para>将布尔值转换为字符串。可以两种方式来使用:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>foo?string("yes", "no")</literal>:
- <emphasis>从 FreeMarker 2.3.23 版本开始废弃:使用 <link
- linkend="ref_builtin_then"><literal>?then("yes",
- "no")</literal></link> 来替代。如果布尔值是true,
- </emphasis>这会返回第一个参数(此处是:<literal>"yes"</literal>),
- 否则返回第二个参数(此处是:<literal>"no"</literal>)。
- 请注意,返回值总是一个字符串;如果参数是数字,那么首先会转换成字符串。
- 也请注意,两个参数是评估过的,不管只有一个会被用到;
- 如果参数不仅仅是文字的话,这也许会有负面影响。</para>
- </listitem>
-
- <listitem>
- <para><literal>foo?string</literal>:
- <emphasis>从 FreeMarker 2.3.20 版本开始废弃:使用 <link
- linkend="ref_builtin_c_boolean"><literal>?c</literal></link>
- 来代替,或者设置 <literal>boolean_format</literal> <link
- linkend="ref_directive_setting">设置项</link>,比如像
- <literal>"yes,no"</literal>,之后转换会自动发生</emphasis>。
- 如果仍然需要知道,这会转换布尔值为字符串,使用默认字符串来显示 true
- 和 false 值。默认情况下,true 被呈现为 <literal>"true"</literal>,
- 而 false 被呈现为 <literal>"false"</literal>。
- 如果使用 FreeMarker 来生成代码,这是很有用的 <emphasis>(但是从 2.3.20 版本开始,
- 请使用 <literal>?c</literal>)</emphasis>,因为这些值不是非本地化(语言,国家)敏感的。
- 要修改这些默认设置,可以使用 <literal>boolean_format</literal> <link
- linkend="ref_directive_setting">设置</link>。</para>
-
- <para role="forProgrammers">请注意,在很少有的情况下,
- 当值是多类型且同时是布尔值和和字符串时,那么将会返回字符串值,所以
- <literal>boolean_format</literal> 设置没有效果。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_builtin_then">
- <title>then</title>
-
- <indexterm>
- <primary>then built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>ternary operator</primary>
- </indexterm>
-
- <indexterm>
- <primary>format; boolean</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始存在。</para>
- </note>
-
- <para>使用于
- <literal><replaceable>booleanExp</replaceable>?then(<replaceable>whenTrue</replaceable>,
- <replaceable>whenFalse</replaceable>)</literal>,就像是类C语言中的三元运算符
- (也就是说,<literal><replaceable>booleanExp</replaceable> ?
- <replaceable>whenTrue</replaceable> :
- <replaceable>whenFalse</replaceable></literal>)。如果
- <literal><replaceable>booleanExp</replaceable></literal> 评估为布尔值 true,
- 那么就评估并返回第一个参数,而若 <literal><replaceable>booleanExp</replaceable></literal>
- 评估为布尔值 false,那么就评估并返回它的第二个参数。当然,三个表达式可以是任意复杂的。
- 参数表达式可以是任意类型,也可以是不同类型。</para>
-
- <para>该内建函数的一个重要特殊属性是只有一个参数表达式会被评估。
- 这和普通的方法调用不同,它们会评估所有的参数表达式,而不管方法是否需要它们。
- 这也就以为着不需要的参数也可以用于不存在的变量而不会引发错误。
- (当然它不能是非法的语法。)</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><#assign foo = true>
-${foo?then('Y', 'N')}
-
-<#assign foo = false>
-${foo?then('Y', 'N')}
-
-<#assign x = 10>
-<#assign y = 20>
-<#-- Prints 100 plus the maximum of x and y: -->
-${100 + (x > y)?then(x, y)}</programlisting>
-
- <programlisting role="output">Y
-
-N
-
-120</programlisting>
-
- <note>
- <para>如果需要选择非布尔值,可以使用 <link
- linkend="ref_builtin_switch"><literal>switch</literal>
- 内建函数</link> 来替代嵌套多个
- <literal>then</literal>,比如
- <literal>priority?switch(1, "low", 2, "medium", 3,
- "high")</literal>,或者是 <literal>true?switch(priority <= 1,
- "low", priority == 2, "medium", priority >= 3,
- "high")</literal>。</para>
- </note>
- </section>
- </section>
-
- <section xml:id="ref_builtins_sequence">
- <title>序列内建函数</title>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <section xml:id="ref_builtin_chunk">
- <title>chunk</title>
-
- <indexterm>
- <primary>chunk built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>tabular printing of sequences</primary>
- </indexterm>
-
- <indexterm>
- <primary>columnar printing of sequences</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.3 版本开始存在。</para>
- </note>
-
- <para>该内建函数将序列分隔为多个序列,长度为第一个参数给定的值
- (比如 <literal>mySeq?chunk(3)</literal>)。结果是包含这些序列的一个序列。
- 最后一个序列可能比给定的长度要小,除非第二个参数也给定了
- (比如 比如 <literal>mySeq?chunk(3, '-')</literal>),
- 这就是用来填充最后一个序列,以达到给定的长度。例如:</para>
-
- <programlisting role="template"><#assign seq = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']>
-
-<#list seq?chunk(4) as row>
- <#list row as cell>${cell} </#list>
-</#list>
-
-<#list seq?chunk(4, '-') as row>
- <#list row as cell>${cell} </#list>
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">
- a b c d
- e f g h
- i j
-
- a b c d
- e f g h
- i j - -
- </programlisting>
-
- <para>该内建函数通常在输出的序列中使用表格/柱状的格式。
- 当被用于HTML表格时,第二个参数通常是<literal>"\xA0"</literal>
- (也就是不换行的空格代码,也就是我们所知的"nbsp"),
- 所以空TD的边界就不会不丢失。</para>
-
- <para>第一个参数必须是一个数字,而且至少是1。如果这个数字不是整数,
- 那么它会被静默地去掉小数部分(也就是说3.1和3.9都会被规整为3)。
- 第二个参数可以是任意类型的值。</para>
- </section>
-
- <section xml:id="ref_builtin_first">
- <title>first</title>
-
- <indexterm>
- <primary>first built-in</primary>
- </indexterm>
-
- <para>序列的第一个子变量。如果序列为空,那么模板处理将会中止。</para>
- </section>
-
- <section xml:id="ref_builtin_join">
- <title>join</title>
-
- <indexterm>
- <primary>join built-in</primary>
- </indexterm>
-
- <para>使用给定的分隔符来连接序列中的项为一个独立的字符串,例如:</para>
-
- <programlisting role="template"><#assign colors = ["red", "green", "blue"]>
-${colors?join(", ")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">red, green, blue</programlisting>
-
- <para>序列中不是字符串的项会被转换为字符串,使用
- <literal>${<replaceable>...</replaceable>}</literal> 相同的转换规则
- (当然这里不会应用自动转义)。</para>
-
- <para><literal>?join(<replaceable>...</replaceable>)</literal>
- 最多可以有3个参数:</para>
-
- <orderedlist>
- <listitem>
- <para>分隔符,是必须的:插入到每一项中间的字符串</para>
- </listitem>
-
- <listitem>
- <para>空值,默认是 <literal>""</literal> (空字符串):
- 如果序列为空,使用该值。</para>
- </listitem>
-
- <listitem>
- <para>列表结尾,默认是 <literal>""</literal> (空字符串):
- 如果列表序列不为空,该值在最后一个值后面输出。</para>
- </listitem>
- </orderedlist>
-
- <para>所以 (这里 <literal>[]</literal> 意味着空序列):</para>
-
- <programlisting role="template">${colors?join(", ", "-")}
-${[]?join(", ", "-")}
-
-${colors?join(", ", "-", ".")}
-${[]?join(", ", "-", ".")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">red, green, blue
--
-
-red, green, blue.
--</programlisting>
-
- <para role="forProgrammers">来自Java的序列可能会包含
- <literal>null</literal> 值。那些值会被该内建函数忽略,
- 就像它们从列表中被移除了。</para>
- </section>
-
- <section xml:id="ref_builtin_last">
- <title>last</title>
-
- <indexterm>
- <primary>last built-in</primary>
- </indexterm>
-
- <para>序列的最后一个子变量。如果序列为空,那么模板处理将会中止。</para>
- </section>
-
- <section xml:id="ref_builtin_reverse">
- <title>reverse</title>
-
- <indexterm>
- <primary>reverse built-in</primary>
- </indexterm>
-
- <para>序列的反序形式。</para>
- </section>
-
- <section xml:id="ref_builtin_seq_contains">
- <title>seq_contains</title>
-
- <indexterm>
- <primary>seq_contains built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。
- 而在 2.3 版本中不存在。</para>
- </note>
-
- <note>
- <para><literal>seq_</literal> 前缀在该内建函数名字中是需要的,
- 用来和 <link
- linkend="ref_builtin_contains"><literal>contains</literal>
- 内建函数</link> 区分开。contains 用来在字符串中查找子串
- (因为变量可以同时当作字符串和序列)。</para>
- </note>
-
- <para>辨别序列中是否包含指定值。它包含一个参数,就是来查找的值。比如:</para>
-
- <programlisting role="template"><#assign x = ["red", 16, "blue", "cyan"]>
-"blue": ${x?seq_contains("blue")?string("yes", "no")}
-"yellow": ${x?seq_contains("yellow")?string("yes", "no")}
-16: ${x?seq_contains(16)?string("yes", "no")}
-"16": ${x?seq_contains("16")?string("yes", "no")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">"blue": yes
-"yellow": no
-16: yes
-"16": no</programlisting>
-
- <para>为了查找值,该内建函数使用了 FreeMarker 的比较规则
- (就像使用 <link
- linkend="dgui_template_exp_comparison"><literal>==</literal>
- 操作符</link>),除了比较两个不同类型的值,或 FreeMarker 不支持的类型来比较,
- 其他都不会引起错误,只是会评估两个值不相等。因此,你可以使用它来查找标量值
- (也就是字符串,数字,布尔值,或日期/时间类型)。
- 对于其他类型结果通常都是 <literal>false</literal>。</para>
-
- <para>对于容错性,该内建函数还对 collection 起作用。</para>
- </section>
-
- <section xml:id="ref_builtin_seq_index_of">
- <title>seq_index_of</title>
-
- <indexterm>
- <primary>seq_index_of built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。
- 而在 2.3 版本中不存在。</para>
- </note>
-
- <note>
- <para><literal>seq_</literal> 前缀在该内建函数名字中是需要的,
- 用来和 <link
- linkend="ref_builtin_index_of"><literal>index_of</literal>
- 内建函数</link> 区分开。index_of 用来在字符串中查找子串
- (因为变量可以同时当作字符串和序列)。</para>
- </note>
-
- <para>返回序列中第一次出现该值时的索引位置,
- 如果序列不包含指定的值时返回 <literal>-1</literal>。
- 要查找的值作为第一个参数。比如这个模板:</para>
-
- <programlisting role="template"><#assign colors = ["red", "green", "blue"]>
-${colors?seq_index_of("blue")}
-${colors?seq_index_of("red")}
-${colors?seq_index_of("purple")}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">2
-0
--1</programlisting>
-
- <para>为了查找值,该内建函数使用了FreeMarker 的比较规则
- (就像使用了 <link
- linkend="dgui_template_exp_comparison"><literal>==</literal>
- 操作符</link>),除了比较两个不同类型的值,或 FreeMarker 不支持的类型来比较,
- 其他都不会引起错误,只是会评估两个值不相等。因此,你可以使用它来查找标量值
- (也就是字符串,数字,布尔值,或日期/时间类型)。
- 对于其他类型结果通常是 <literal>-1</literal>。</para>
-
- <para>搜索开始的索引值可以由第二个可选参数来确定。
- 如果在同一个序列中相同的项可以多次出现时,这是很有用的。
- 第二个参数的数值没有什么限制:如果它是负数,那么就和它是零的效果一样,
- 而如果它是比序列长度还大的数,那么就和它是序列长度值的效果一样。
- 小数值会被切成整数。比如:</para>
-
- <programlisting role="template"><#assign names = ["Joe", "Fred", "Joe", "Susan"]>
-No 2nd param: ${names?seq_index_of("Joe")}
--2: ${names?seq_index_of("Joe", -2)}
--1: ${names?seq_index_of("Joe", -1)}
- 0: ${names?seq_index_of("Joe", 0)}
- 1: ${names?seq_index_of("Joe", 1)}
- 2: ${names?seq_index_of("Joe", 2)}
- 3: ${names?seq_index_of("Joe", 3)}
- 4: ${names?seq_index_of("Joe", 4)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">No 2nd param: 0
--2: 0
--1: 0
- 0: 0
- 1: 2
- 2: 2
- 3: -1
- 4: -1</programlisting>
- </section>
-
- <section xml:id="ref_builtin_seq_last_index_of">
- <title>seq_last_index_of</title>
-
- <indexterm>
- <primary>seq_last_index_of built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.1 版本开始可用。而在2.3版本中不存在。</para>
- </note>
-
- <note>
- <para><literal>seq_</literal> 前缀在该内建函数名字中是需要的,
- 用来和 <link
- linkend="ref_builtin_last_index_of"><literal>last_index_of</literal>
- 内建函数</link> 区分开。last_index_of 用来在字符串中查找子串
- (因为变量可以同时当作字符串和序列)。</para>
- </note>
-
- <para>返回序列中最后一次出现值的索引位置,
- 如果序列不包含指定的值时返回 <literal>-1</literal>。也就是说,和 <link
- linkend="ref_builtin_seq_index_of"><literal>seq_index_of</literal></link> 相同,
- 只是在序列中从最后一项开始向前搜索。
- 它也支持可选的第二个参数来确定从哪里开始搜索的索引位置。比如:</para>
-
- <programlisting role="template"><#assign names = ["Joe", "Fred", "Joe", "Susan"]>
-No 2nd param: ${names?seq_last_index_of("Joe")}
--2: ${names?seq_last_index_of("Joe", -2)}
--1: ${names?seq_last_index_of("Joe", -1)}
- 0: ${names?seq_last_index_of("Joe", 0)}
- 1: ${names?seq_last_index_of("Joe", 1)}
- 2: ${names?seq_last_index_of("Joe", 2)}
- 3: ${names?seq_last_index_of("Joe", 3)}
- 4: ${names?seq_last_index_of("Joe", 4)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">No 2nd param: 2
--2: -1
--1: -1
- 0: 0
- 1: 0
- 2: 2
- 3: 2
- 4: 2</programlisting>
- </section>
-
- <section xml:id="ref_builtin_size">
- <title>size</title>
-
- <indexterm>
- <primary>size built-in</primary>
- </indexterm>
-
- <para>序列中子变量的数量(作为数字值)。假设序列中至少有一个子变量,
- 那么序列 <literal>s</literal> 中最大的索引是 <literal>s?size - 1</literal>
- (因为第一个子变量的序列是0)。</para>
- </section>
-
- <section xml:id="ref_builtin_sort">
- <title>sort</title>
-
- <indexterm>
- <primary>sort built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>sorting</secondary>
- </indexterm>
-
- <para>以升序方式返回序列。(要使用降序排列时,使用它之后使用 <link
- linkend="ref_builtin_reverse"><literal>reverse</literal> 内建函数</link>。)
- 这仅在子变量都是字符串时有效,或者子变量都是数字,或者子变量都是日期值
- (日期,时间,或日期+时间),或者所有子变量都是布尔值时(从2.3.17版本开始)。
- 如果子变量是字符串,它使用本地化(语言)的具体单词排序(通常是大小写不敏感的)。比如:</para>
-
- <programlisting role="template"><#assign ls = ["whale", "Barbara", "zeppelin", "aardvark", "beetroot"]?sort>
-<#list ls as i>${i} </#list></programlisting>
-
- <para>将会输出(至少是US区域设置):</para>
-
- <programlisting role="output">aardvark Barbara beetroot whale zeppelin</programlisting>
- </section>
-
- <section xml:id="ref_builtin_sort_by">
- <title>sort_by</title>
-
- <indexterm>
- <primary>sort_by built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>sorting</secondary>
- </indexterm>
-
- <para>返回由给定的哈希表子变量来升序排序的哈希表序列。
- (要降序排列使用该内建函数后还要使用 <link
- linkend="ref_builtin_reverse"><literal>reverse</literal> 内建函数</link>。)
- 这个规则和 <link
- linkend="ref_builtin_sort"><literal>sort</literal> 内建函数</link> 是一样的,
- 除了序列中的子变量必须是哈希表类型,而且你不得不给哈希变量的命名,
- 那会用来决定排序顺序。比如:</para>
-
- <programlisting role="template"><#assign ls = [
- {"name":"whale", "weight":2000},
- {"name":"Barbara", "weight":53},
- {"name":"zeppelin", "weight":-200},
- {"name":"aardvark", "weight":30},
- {"name":"beetroot", "weight":0.3}
-]>
-Order by name:
-<#list ls?sort_by("name") as i>
-- ${i.name}: ${i.weight}
-</#list>
-
-Order by weight:
-<#list ls?sort_by("weight") as i>
-- ${i.name}: ${i.weight}
-</#list></programlisting>
-
- <para>将会输出(至少是US区域设置):</para>
-
- <programlisting role="output">Order by name:
-- aardvark: 30
-- Barbara: 53
-- beetroot: 0.3
-- whale: 2000
-- zeppelin: -200
-
-Order by weight:
-- zeppelin: -200
-- beetroot: 0.3
-- aardvark: 30
-- Barbara: 53
-- whale: 2000</programlisting>
-
- <para>如果你用来排序的子变量的层次很深
- (也就是说,它是子变量的子变量的子变量,以此类推),
- 那么你可以使用序列来作为参数,它指定了子变量的名字,
- 来向下引导所需的子变量。比如:</para>
-
- <programlisting role="template"><#assign members = [
- {"name": {"first": "Joe", "last": "Smith"}, "age": 40},
- {"name": {"first": "Fred", "last": "Crooger"}, "age": 35},
- {"name": {"first": "Amanda", "last": "Fox"}, "age": 25}]>
-Sorted by name.last:
-<#list members?sort_by(['name', 'last']) as m>
-- ${m.name.last}, ${m.name.first}: ${m.age} years old
-</#list></programlisting>
-
- <para>将会输出(至少是US区域设置):</para>
-
- <programlisting role="output">Sorted by name.last:
-- Crooger, Fred: 35 years old
-- Fox, Amanda: 25 years old
-- Smith, Joe: 40 years old</programlisting>
- </section>
- </section>
-
- <section xml:id="ref_builtins_hash">
- <title>哈希表内建函数</title>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <section xml:id="ref_builtin_keys">
- <title>keys</title>
-
- <indexterm>
- <primary>keys built-in</primary>
- </indexterm>
-
- <para>一个包含哈希表中查找到的键的序列。
- 请注意,并不是所有的哈希表都支持这个
- (询问程序员一个指定的哈希表是否允许这么操作)。</para>
-
- <programlisting role="template"><#assign h = {"name":"mouse", "price":50}>
-<#assign keys = h?keys>
-<#list keys as key>${key} = ${h[key]}; </#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">name = mouse; price = 50;</programlisting>
-
- <para>因为哈希表通常没有定义子变量的顺序,那么键名称的返回顺序就是任意的。
- 然而,一些哈希表维持一个有意义的顺序(询问程序员指定的哈希表是否是这样)。
- 比如,由上述 <literal>{<replaceable>...</replaceable>}</literal>
- 语法创建的哈希表保存了和你指定子变量相同的顺序。</para>
- </section>
-
- <section xml:id="ref_builtin_values">
- <title>values</title>
-
- <indexterm>
- <primary>values built-in</primary>
- </indexterm>
-
- <para>一个包含哈希表中子变量的序列。
- 注意并不是所有的哈希表都支持这个
- (询问程序员一个指定的哈希表是否允许这么操作)。</para>
-
- <para>至于返回的值的顺序,和内建函数 <literal>keys</literal>
- 的应用是一样的;看看上面的叙述就行了。</para>
- </section>
- </section>
-
- <section xml:id="ref_builtins_node">
- <title>结点(对于XML)内建函数</title>
-
- <indexterm>
- <primary>node</primary>
-
- <secondary>built-ins</secondary>
- </indexterm>
-
- <para>请注意,由这些内建函数返回的变量是由用于结点变量的实现生成的。
- 意思就是返回的变量可以有更多的特性,附加于它这里的状态。
- 比如,由内建函数 <literal>children</literal> 返回的序列和 <link
- linkend="xgui_expose_dom">XML DOM 结点</link> 也可以被用作是哈希表或字符串,
- 这在 <link linkend="xgui">XML 处理部分</link> 中有描述。</para>
-
- <section xml:id="ref_builtin_ancestors">
- <title>ancestors</title>
-
- <indexterm>
- <primary>ancestors built-in</primary>
- </indexterm>
-
- <para>一个包含所有结点祖先结点的序列,以直接父结点开始,以根结点结束。
- 该内建函数的结果也是一个方法,你可以用它和元素的 <link
- linkend="gloss.fullQualifiedName">完全限定名</link> 来过滤结果。
- 比如以名称 <literal>section</literal> 用
- <literal>node?ancestors("section")</literal> 来获得所有祖先结点的序列。</para>
- </section>
-
- <section xml:id="ref_builtin_children">
- <title>children</title>
-
- <indexterm>
- <primary>children built-in</primary>
- </indexterm>
-
- <para>一个包含该结点所有子结点(也就是直接后继结点)的序列。</para>
-
- <para>XML:这和特殊的哈希表的键 <literal>*</literal> 几乎是一样的。
- 除了它返回所有结点,而不但是元素。所以可能的子结点是元素结点,
- 文本结点,注释结点,处理指令结点等,但 <emphasis>不是</emphasis>
- 属性结点。属性结点排除在序列之外。</para>
- </section>
-
- <section xml:id="ref_builtin_node_name">
- <title>node_name</title>
-
- <indexterm>
- <primary>node_name built-in</primary>
- </indexterm>
-
- <para>当被"访问"时,返回用来决定哪个自定义指令来调用控制这个结点的字符串。
- 可以参见 <link linkend="ref.directive.visit">visit</link> 和 <link
- linkend="ref.directive.recurse">recurse</link> 指令。</para>
-
- <para>XML:如果结点是元素或属性,那么字符串就会是元素或属性的本地
- (没有前缀)名字。否则,名称通常在结点类型之后以 <literal>@</literal> 开始。
- 可以参见 <link linkend="misc.xguiTable">该表格</link>。
- 要注意这个结点名称与在DOM API中返回的结点名称不同;
- FreeMarker 结点名称的目标是给要处理结点的用户自定义指令命名。</para>
- </section>
-
- <section xml:id="ref_builtin_node_namespace">
- <title>node_namespace</title>
-
- <indexterm>
- <primary>node_namespace built-in</primary>
- </indexterm>
-
- <para>返回结点命名空间的字符串。FreeMarker 没有为结点命名空间定义准确的含义;
- 它依赖于变量是怎么建模的。也可能结点没有定义任何结点命名空间。
- 这种情形下,该函数应该返回未定义的变量
- (也就是 <literal>node?<replaceable>node_namespace</replaceable>??</literal>
- 的值是 <literal>false</literal>),所以不能使用这个返回值。</para>
-
- <para>XML:这种情况下的XML,就是XML命名空间的URI
- (比如 <literal>"http://www.w3.org/1999/xhtml"</literal>)。
- 如果一个元素或属性结点没有使用XML命名空间,那么这个函数就返回一个空字符串。
- 对于其他XML结点这个函数返回未定义的变量。</para>
- </section>
-
- <section xml:id="ref_builtin_node_type">
- <title>node_type</title>
-
- <indexterm>
- <primary>node_type built-in</primary>
- </indexterm>
-
- <para>描述结点类型的字符串。FreeMarker 没有对结点类型定义准确的含义;
- 它依赖于变量是怎么建模的。也可能结点并不支持结点类型。
- 在这种情形下,该函数就返回未定义值,所以就不能使用返回值。
- (可以用 <literal><replaceable>node</replaceable>?node_type??</literal>
- 继续检查一个结点是否是支持类型属性。)</para>
-
- <para>XML:可能的值是: <literal>"attribute"</literal>,
- <literal>"text"</literal>, <literal>"comment"</literal>,
- <literal>"document_fragment"</literal>,
- <literal>"document"</literal>, <literal>"document_type"</literal>,
- <literal>"element"</literal>, <literal>"entity"</literal>,
- <literal>"entity_reference"</literal>,
- <literal>"notation"</literal>, <literal>"pi"</literal>。
- 请注意,没有 <literal>"cdata"</literal> 类型,因为CDATA被认为是普通文本元素。</para>
- </section>
-
- <section xml:id="ref_builtin_parent">
- <title>parent</title>
-
- <indexterm>
- <primary>parent built-in</primary>
- </indexterm>
-
- <para>在结点树中,返回该结点的直接父结点。根结点没有父结点,
- 所以对于根结点,表达式
- <literal><replaceable>node</replaceable>?parent??</literal>
- 的值就是 <literal>false</literal>。</para>
-
- <para>XML:注意通过这个函数返回的值也是一个序列
- (当编写 <literal>someNode[".."]</literal> 时,和XPath表达式
- <literal>..</literal> 的结果是一样的)。也要注意属性结点,
- 它返回属性所属的元素结点,尽管属性结点不被算作是元素的子结点。</para>
- </section>
-
- <section xml:id="ref_builtin_root">
- <title>root</title>
-
- <indexterm>
- <primary>root built-in</primary>
- </indexterm>
-
- <para>该结点所属结点树的根结点。</para>
-
- <para>XML:根据W3C,XML文档的根结点不是最顶层的元素结点,
- 而是文档本身,是最高元素的父结点。例如,
- 如果想得到被称为是 <literal>foo</literal> 的XML
- (所谓的"文档元素",不要和"文档"搞混了)的最高 <emphasis>元素</emphasis>,
- 那么不得不编写 <literal>someNode?root.foo</literal>。如果仅仅写了
- <literal>someNode?root</literal>,那么得到的是文档本身,而不是文档元素。</para>
- </section>
- </section>
-
- <section xml:id="ref_builtins_loop_var">
- <title>循环变量内建函数</title>
-
- <note>
- <para>循环变量内建函数从 FreeMarker 2.3.23 版本开始存在。</para>
- </note>
-
- <para>这些内建函数只能用于<link
- linkend="ref_directive_list"><literal>list</literal> 和
- <literal>items</literal> 指令</link> 的循环变量
- (也可以用于已经废弃的 <literal>foreach</literal> 指令)。
- 下面是一些说明(<literal><replaceable>loopVar</replaceable>?index</literal>
- 在可以列表的值中进行迭代,返回从0开始的索引):</para>
-
- <programlisting role="template"><#-- Note: x is a loop variable -->
-<#list ['a', 'b', 'c'] as x>
- ${x?index}
-</#list></programlisting>
-
- <programlisting role="output">0
-1
-2</programlisting>
-
- <para>当 <literal>list</literal> 指令不指定循环变量时,
- 这些内建函数就作用于 <literal>items</literal> 指令的循环变量:</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c']>
- <ul>
- <#items as x>
- <li>${x?index}</li>
- </#items>
- </ul>
-</#list></programlisting>
-
- <para>循环变量内建函数仅仅用于循环变量的 <emphasis>名字</emphasis>,
- 所以它们可以识别相关进行的迭代。它们不读取循环变量的
- <emphasis>值</emphasis>。因此,这就会有解析错误:</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as x>
- <#assign y = x>
- ${y?index} <#-- ERROR: y isn't a loop variable -->
-</#list></programlisting>
-
- <section xml:id="ref_builtin_counter">
- <title>counter</title>
-
- <indexterm>
- <primary>counter built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>返回当前迭代(由循环变量名称识别)从1开始的索引。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as i>
- ${i?counter}: ${i}
-</#list></programlisting>
-
- <programlisting role="output"> 1: a
- 2: b
- 3: c</programlisting>
-
- <note>
- <para>要从0开始的索引,请使用 <link
- linkend="ref_builtin_index"><literal>index</literal>
- 内建函数</link>。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_has_next">
- <title>has_next</title>
-
- <indexterm>
- <primary>has_next built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>辨别循环项是否是当前迭代(由循环变量名称识别)的最后一项。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as i>${i?has_next?c} </#list></programlisting>
-
- <programlisting role="output">true true false </programlisting>
-
- <note>
- <para>使用逗号等隔开循环项,请使用
- <literal><#sep><replaceable>separator</replaceable></#sep></literal>
- 来代替 <literal><#if
- <replaceable>var</replaceable>?has_next><replaceable>separator</replaceable></#if></literal>,这样可读性更强。(此外
- <literal></#sep></literal> 经常被忽略,比如在
- <literal><#list <replaceable>...</replaceable> as
- <replaceable>var</replaceable>><replaceable>...</replaceable>${<replaceable>var</replaceable>}<replaceable>...</replaceable><#sep><replaceable>separator</replaceable></#list></literal>
- 中)</para>
- </note>
-
- <note>
- <para>如果需要对该内建函数取反,请使用
- <literal><replaceable>var</replaceable>?is_last</literal> 来代替
- <literal>!<replaceable>var</replaceable>?has_next</literal>,
- 因为它的可读性更强。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_index">
- <title>index</title>
-
- <indexterm>
- <primary>index built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>返回当前迭代(由循环变量名称识别)从0开始的索引。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as i>
- ${i?index}: ${i}
-</#list></programlisting>
-
- <programlisting role="output"> 0: a
- 1: b
- 2: c</programlisting>
-
- <note>
- <para>要从1开始的索引,请使用 <link
- linkend="ref_builtin_counter"><literal>counter</literal>
- 内建函数</link>。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_is_even_item">
- <title>is_even_item</title>
-
- <indexterm>
- <primary>is_even_item built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>辨别循环项是否是当前迭代(由循环变量名称识别)间隔1的奇数项。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c', 'd'] as i>${i?is_even_item?c} </#list></programlisting>
-
- <programlisting role="output">false true false true</programlisting>
-
- <note>
- <para>要将表格进行行间变色等操作,请使用
- <link
- linkend="ref_builtin_item_parity"><literal><replaceable>var</replaceable>?item_parity</literal></link>
- 或 <link
- linkend="ref_builtin_item_cycle"><literal><replaceable>var</replaceable>?item_cycle(...)</literal></link>
- 来代替。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_is_first">
- <title>is_first</title>
-
- <indexterm>
- <primary>is_first built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>辨别循环项是否是当前迭代(由循环变量名称识别)的第一项。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as i>${i?is_first?c} </#list></programlisting>
-
- <programlisting role="output">true false false </programlisting>
- </section>
-
- <section xml:id="ref_builtin_is_last">
- <title>is_last</title>
-
- <indexterm>
- <primary>is_last built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>辨别循环项是否是当前迭代(由循环变量名称识别)的最后一项。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c'] as i>${i?is_last?c} </#list></programlisting>
-
- <programlisting role="output">false false true</programlisting>
-
- <note>
- <para>如果需要对该内建函数取反,请使用
- <literal><replaceable>var</replaceable>?has_next</literal> 来代替
- <literal>!<replaceable>var</replaceable>?is_last</literal>,
- 因为它的可读性更强。</para>
- </note>
-
- <note>
- <para>使用逗号等隔开循环项,请使用
- <literal><#sep><replaceable>separator</replaceable></#sep></literal>
- 来代替 <literal><#if
- <replaceable>var</replaceable>?has_next><replaceable>separator</replaceable></#if></literal>,因为它的可读性更强。(此外
- <literal></#sep></literal> 经常被忽略,比如在
- <literal><#list <replaceable>...</replaceable> as
- <replaceable>var</replaceable>><replaceable>...</replaceable>${<replaceable>var</replaceable>}<replaceable>...</replaceable><#sep><replaceable>separator</replaceable></#list></literal>
- 中)</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_is_odd_item">
- <title>is_odd_item</title>
-
- <indexterm>
- <primary>is_odd_item built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>辨别循环项是否是当前迭代(由循环变量名称识别)间隔1的偶数项。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c', 'd'] as i>${i?is_odd_item?c} </#list></programlisting>
-
- <programlisting role="output">true false true false </programlisting>
-
- <note>
- <para>要将表格进行行间变色等操作,请使用
- <link
- linkend="ref_builtin_item_parity"><literal><replaceable>var</replaceable>?item_parity</literal></link>
- 或 <link
- linkend="ref_builtin_item_cycle"><literal><replaceable>var</replaceable>?item_cycle(...)</literal></link>
- 来代替。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_item_cycle">
- <title>item_cycle</title>
-
- <indexterm>
- <primary>item_cycle built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>这是 <link
- linkend="ref_builtin_item_parity"><literal>item_parity</literal>
- 内建函数</link> 更为通用的版本,这里可以指定何值来代替
- <literal>"odd"</literal> 和 <literal>"even"</literal>。
- 它也允许多余两个值来循环。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c', 'd', 'e', 'f', 'g'] as i>
- <tr class="${i?item_cycle('row1', 'row2', 'row3')}">${i}</tr>
-</#list></programlisting>
-
- <programlisting role="output"> <tr class="row1">a</tr>
- <tr class="row2">b</tr>
- <tr class="row3">c</tr>
- <tr class="row1">d</tr>
- <tr class="row2">e</tr>
- <tr class="row3">f</tr>
- <tr class="row1">g</tr></programlisting>
-
- <para>一些细节:</para>
-
- <itemizedlist>
- <listitem>
- <para>参数的个数至少是1个,没有上限。</para>
- </listitem>
-
- <listitem>
- <para>参数的类型是任意的,无需只是字符串。</para>
- </listitem>
- </itemizedlist>
-
- <note>
- <para>如果需要<literal>"odd"</literal>
- 和 <literal>"even"</literal>,请使用 <link
- linkend="ref_builtin_item_parity"><literal>item_parity</literal>
- 内建函数</link> 来代替。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_item_parity">
- <title>item_parity</title>
-
- <indexterm>
- <primary>item_parity built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>基于当前迭代(由循环变量名称识别)间隔为1的索引的奇偶性,
- 返回字符串值 <literal>"odd"</literal> 或 <literal>"even"</literal>。
- 这通常用于表格中行间的颜色变换:</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c', 'd'] as i>
- <tr class="${i?item_parity}Row">${i}</tr>
-</#list></programlisting>
-
- <programlisting role="output"> <tr class="oddRow">a</tr>
- <tr class="evenRow">b</tr>
- <tr class="oddRow">c</tr>
- <tr class="evenRow">d</tr></programlisting>
-
- <note>
- <para>请使用 <link
- linkend="ref_builtin_item_parity"><literal>item_parity_cap</literal>
- 内建函数</link> 来大写 <literal>"Odd"</literal> 和
- <literal>"Even"</literal>。请使用 <link
- linkend="ref_builtin_item_cycle"><literal>item_cycle</literal>
- 内建函数</link> 来指定自定义值,或多于两个值。</para>
- </note>
- </section>
-
- <section xml:id="ref_builtin_item_parity_cap">
- <title>item_parity_cap</title>
-
- <indexterm>
- <primary>item_parity_cap built-in</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>基于当前迭代(由循环变量名称识别)间隔为1的索引的奇偶性,
- 返回字符串值 <literal>"Odd"</literal> 或 <literal>"Even"</literal>
- (请注意大写)。</para>
-
- <programlisting role="template"><#list ['a', 'b', 'c', 'd'] as i>
- <tr class="row${i?item_parity_cap}">${i}</tr>
-</#list></programlisting>
-
- <programlisting role="output"> <tr class="rowOdd">a</tr>
- <tr class="rowEven">b</tr>
- <tr class="rowOdd">c</tr>
- <tr class="rowEven">d</tr></programlisting>
-
- <note>
- <para>请使用 <link
- linkend="ref_builtin_item_parity"><literal>item_parity</literal>
- 内建函数</link> 来小写 <literal>"odd"</literal> 和
- <literal>"even"</literal>。</para>
- </note>
- </section>
- </section>
-
- <section xml:id="ref_builtins_type_independent">
- <title>独立类型内建函数</title>
-
- <para>这些内建函数不(更多)关心它们左侧参数的类型。</para>
-
- <section xml:id="ref_builtin_switch">
- <title>switch</title>
-
- <indexterm>
- <primary>switch built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>switch expression</primary>
- </indexterm>
-
- <note>
- <para>该内建函数从 FreeMarker 2.3.23 版本开始可用。</para>
- </note>
-
- <para>这是
- <link
- linkend="ref_directive_switch"><literal>switch</literal>-<literal>case</literal>-<literal>default</literal>
- 指令</link> 的基本内联(表达式)版本。它的通用版本就像
- <literal><replaceable>matchedValue</replaceable>?switch(<replaceable>case1</replaceable>,
- <replaceable>result1</replaceable>,
- <replaceable>case2</replaceable>,
- <replaceable>result2</replaceable>, ...
- <replaceable>caseN</replaceable>,
- <replaceable>resultN</replaceable>,
- <replaceable>defaultResult</replaceable>)</literal>,这里的
- <literal><replaceable>defaultResult</replaceable></literal>
- 可以被忽略。比如:</para>
-
- <programlisting role="template"><#list ['r', 'w', 'x', 's'] as flag>
- ${flag<emphasis>?switch('r', 'readable', 'w' 'writable', 'x', 'executable', 'unknown flag: ' + flag)</emphasis>}
-</#list></programlisting>
-
- <programlisting role="output"> readable
- writable
- executable
- unknown flag: s
-</programlisting>
-
- <para>也就是说, <literal>switch</literal> 会找到第一个
- <literal><replaceable>case</replaceable></literal> 和参数
- (从左到右)值
- <literal><replaceable>matchedValue</replaceable></literal> 相等,
- 之后返回直接在
- <literal><replaceable>case</replaceable></literal>
- 参数后的
- <literal><replaceable>result</replaceable></literal> 参数的值,
- 如果它没有找到一个相等的
- <literal><replaceable>case</replaceable></literal>,那么就返回
- <literal><replaceable>defaultResult</replaceable></literal> 的值,如果没有
- <literal><replaceable>defaultResult</replaceable></literal> 参数
- (换言之,参数的个数是基数),那么就发生错误中止模板处理。</para>
-
- <para>更多细节:</para>
-
- <itemizedlist>
- <listitem>
- <para>The comparison of
- <literal><replaceable>matchedValue</replaceable></literal> 和
- <literal><replaceable>case</replaceable></literal> 参数值的比较,
- 就像 <link
- linkend="dgui_template_exp_comparison"> <literal>==</literal>
- 操作符</link>。那就只比较标量并且是相同类型的值。因此,诸如
- <literal>x?switch(1, "r1", "c2", "r2")</literal> 就没有意思。就像
- <literal>x</literal> 是非数字值,那么第一个case就会引发错误,
- 若 <literal>x</literal> 是数字值,那么第二个case就会引发错误
- (除非 <literal>x</literal> 是 <literal>1</literal>,
- 那么就不会在第一个参数之后做更多的比较)。</para>
- </listitem>
-
- <listitem>
- <para>不像普通的方法调用,
- <literal>switch(<replaceable>...</replaceable>)</literal>
- 的那些参数被评估为确实需要的。比如,在
- <literal>two()?switch(c1(), r1(), c2(), r2(), c3(),
- r3())</literal> 中,如果 <literal>two()</literal> 返回
- <literal>2</literal>, <literal>c1()</literal> 返回
- <literal>1</literal>, 且 <literal>c2()</literal> 返回
- <literal>2</literal>,那么只有下面的函数会被调用,而且顺序是这样:
- <literal>m()</literal>,
- <literal>c1()</literal>,<literal>c2()</literal>,
- <literal>r2()</literal>。(很自然地,
- 参数不被评估可以指向不存在的变量而不会引发错误。)
- 它保证了
- <literal><replaceable>case</replaceable></literal>
- 参数表达式被从左到右进行评估,直到第一个匹配项被找到。
- 它也保证了只有属于第一个匹配
- <literal><replaceable>case</replaceable></literal> 的
- <literal><replaceable>result</replaceable></literal>
- 表达式会被评估。它还保证了如果没有匹配的
- <literal><replaceable>case</replaceable></literal>
- 参数,那么
- <literal><replaceable>defaultResult</replaceable></literal>
- 表达式会被评估。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>case</replaceable></literal>
- 参数表达式不需要是常量值,它们可以是任意复杂的表达式。
- 当然,逻辑在
- <literal><replaceable>result</replaceable></literal>,
- <literal><replaceable>defaultResult</replaceable></literal> 和
- <literal><replaceable>matchedValue</replaceable></literal>
- 中也是相同的。</para>
- </listitem>
-
- <listitem>
- <para>对 <literal><replaceable>case</replaceable></literal>
- 参数值的类型没有任何限制,比如它们可以是字符串,或数字,或日期等...
- 但因为 <literal>==</literal> 操作符的特性,那么在
- <replaceable>相同的</replaceable>
- <literal>switch</literal> 中使用不同类型的
- <literal><replaceable>case</replaceable></literal> 参数是没有意义的
- (请参考之前的解释)。</para>
- </listitem>
-
- <listitem>
- <para>不像使用 <link
- linkend="ref_directive_switch"><literal>case</literal>
- 指令</link>,那里没有向下通过的行为,也就是说,不需要相等的
- <literal>break</literal> 指令。</para>
- </listitem>
- </itemizedlist>
-
- <note>
- <para>如果需要对布尔值进行switch操作,那么应该使用
- <link linkend="ref_builtin_then"><literal>then</literal>
- 内建函数</link> 来代替,比如
- <literal><replaceable>matchedBoolean</replaceable>?then(<replaceable>whenTrue</replaceable>,
- <replaceable>whenFalse</replaceable>)</literal>。</para>
- </note>
-
- <note>
- <para>如果需要在 <literal><replaceable>case</replaceable></literal>
- 参数中进行任意的逻辑测试来代替简单的相等比较,那么可以这么来做
- (这里我们测试范围):
- <literal>true?switch(priority <= 1, "low", priority == 2,
- "medium", priority >= 3, "high")</literal></para>
- </note>
- </section>
- </section>
-
- <section xml:id="ref_builtins_expert">
- <title>很少使用的和专家级的内建函数</title>
-
- <para>这些是你通常情况下不应该使用的内建函数,
- 但是在特殊情况下(调试,高级宏)它们会有用。
- 如果你需要在普通页面模板中使用这些函数,
- 你可能会重新访问数据模型,所以你不要使用它们。</para>
-
- <section xml:id="ref_buitin_api_and_has_api">
- <title>api, has_api</title>
-
- <indexterm>
- <primary>api built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>has_api built-in</primary>
- </indexterm>
-
- <note>
- <para>这些内建函数从 FreeMarker 2.3.22 版本开始存在。</para>
- </note>
-
- <para>如果value本身支持这个额外的特性,
- <literal><replaceable>value</replaceable>?api</literal>
- 提供访问 <literal><replaceable>value</replaceable></literal> 的API
- (通常是 Java API),比如
- <literal><replaceable>value</replaceable>?api.<replaceable>someJavaMethod()</replaceable></literal>,
- 当需要调用对象的Java方法时,这种方式很少使用,
- 但是 FreeMarker 揭示的value的简化视图的模板隐藏了它,也没有相等的内建函数。
- 例如,当有一个 <literal>Map</literal>,并放入数据模型
- (使用默认的对象包装器),模板中的
- <literal>myMap.myMethod()</literal> 基本上翻译成Java的 <literal>((Method)
- myMap.get("myMethod")).invoke(...)</literal>,因此不能调用
- <literal>myMethod</literal>。如果编写了
- <literal>myMap?api.myMethod()</literal> 来代替,那么就是Java中的
- <literal>myMap.myMethod()</literal>。</para>
-
- <para>尽可能地<emphasis>应避免使用 <literal>api</literal>,
- 基于FTL类型和相关内建函数的功能。</emphasis> 例如,不要使用
- <literal>users?api.size()</literal>,但可以使用
- <literal>users?size</literal>。使用
- <literal>?api</literal> 的变化是很累赘的,更慢,
- 当 FreeMarker 配置设置修改时容易失败,最重要的是,
- 当数据模型的技术细节改变时更容易失败。例如,如果
- <literal>users</literal> 由 <literal>List</literal>
- 变为数组,那么 <literal>users?size</literal> 继续有效,而
- <literal>users?api.size()</literal> 就会失败。</para>
-
- <para>由于一些原因,避免调用 <emphasis>修改</emphasis>
- 对象 (特别是 <literal>Map</literal> 和
- <literal>Collection</literal>) 或非线程安全的方法。
- 模板通常也不希望去修改暴露于它们的对象,只是展示它们。
- 因此应用程序可能会传递一些对象到多个(可能是并发的)模板处理中。</para>
-
- <para><literal>api</literal> 内建函数不是到处都可用的,
- 有一些需求会遇到:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>api_builtin_enabled</literal> 配置设置项必须设置为
- <literal>true</literal>。为了不降低已有应用程序的安全性,它的默认值是
- <literal>false</literal> (至少在 2.3.22 版本中)。</para>
- </listitem>
-
- <listitem>
- <para>值本身要支持它。我们在讨论当模板看到的值,它是通过 <link
- linkend="pgui_datamodel_objectWrapper">对象包装</link>
- 从原始对象值(来自于数据模型或者Java方法的返回值)中创建的。
- 因此,这就依赖 FreeMarker 的配置设置项 <literal>object_wrapper</literal>,
- 还有被包装的(原始)对象:</para>
-
- <itemizedlist>
- <listitem>
- <para>当对象包装器是
- <literal>DefaultObjectWrapper</literal> ,并且它的
- <literal>incompatibleImprovements</literal> 设置为 2.3.22 或更高
- (<link linkend="topic.defaultObjectWrapperIcI">在这里看如何设置它</link>)
- (事实上,要做的是将它的 <literal>useAdaptersForContainer</literal>
- 属性设置为 <literal>true</literal>,但那是提到的
- <literal>incompatibleImprovements</literal> 的默认值)时,从
- <literal>Map</literal> 和 <literal>List</literal>
- 中得到FTL值支持 <literal>?api</literal>。其它的
- <literal>java.util.Collections</literal> 也是这样,如果
- <literal>DefaultObjectWrapper</literal> 的
- <literal>forceLegacyNonListCollections</literal> 属性设置为
- <literal>false</literal> (默认是 <literal>true</literal>,
- 这是为了更好的向后兼容拆包)。</para>
- </listitem>
-
- <listitem>
- <para>当被纯
- <literal>BeansWrapper</literal> 包装时,所有值都支持
- <literal>?api</literal>。但是再次重申,如果有其它方法,就避免使用它。</para>
- </listitem>
-
- <listitem>
- <para>实现了 <literal>freemarker.template.TemplateModelWithAPISupport</literal> 接口,
- 自定义的 <literal>TemplateModel</literal> 可以支持
- <literal>?api</literal>。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <para>当在配置中不允许或值本身不支持 <literal>?api</literal> 时使用了它,
- 就会中止模板处理并发生错误。</para>
-
- <para>一个值是否支持 <literal>?api</literal> 可以通过
- <literal><replaceable>value</replaceable>?has_api</literal> 来检测,
- 返回一个布尔值。请注意,<literal>?has_api</literal> 的结果不受
- <literal>api_builtin_enabled</literal> 设置的影响。</para>
- </section>
-
- <section xml:id="ref_builtin_numType">
- <title>byte, double, float, int, long, short</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>byte built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>double built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>float built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>int built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>long built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>short built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting date to long</primary>
- </indexterm>
-
- <indexterm>
- <primary>date to long</primary>
- </indexterm>
-
- <para>返回一个包含原变量中相同值的 <literal>SimpleNumber</literal>,
- 但是在内部表示值中使用了 <literal>java.lang.<replaceable>Type</replaceable></literal>。
- 如果方法被重载了,这是有用的,或者一个 <literal>TemplateModel</literal>
- 解包器在自动选择适合的 <literal>java.lang.*</literal> 类型有问题时。
- 请注意,从2.3.9版本开始,解包器有本质上改进,
- 所以将基本不会使用到这些内建函数来转换数字类型了,
- 除非在重载方法调用中来解决一些含糊的东西。</para>
-
- <para>内建函数 <literal>long</literal> 也可以用于日期,
- 时间和时间日期类型的值来获取返回为 <literal>java.util.Date.getTime()</literal>
- 的值。如果你不得不调用使用 <literal>long</literal> 类型时间戳的Java方法时,
- 这是非常有用的。这个转换不是自动进行的。</para>
- </section>
-
- <section xml:id="ref_builtin_eval">
- <title>eval</title>
-
- <indexterm>
- <primary>eval</primary>
- </indexterm>
-
- <indexterm>
- <primary>evaluate string</primary>
- </indexterm>
-
- <para>这个函数求一个作为FTL表达式的字符串的值。比如
- <literal>"1+2"?eval</literal>返回数字3。</para>
-
- <para>在调用 <literal>eval</literal> 的地方,
- 已被求值的表达式看到相同的变量(比如本地变量)是可见的。
- 也就是说,它的行为就像在
- <literal><replaceable>s</replaceable>?eval</literal> 处,
- 你有 <literal><replaceable>s</replaceable></literal> 的
- <emphasis>值</emphasis>。除了,指向在
- <literal><replaceable>s</replaceable></literal>
- 之外创建的循环变量,它不能使用
- <link linkend="ref_builtins_loop_var">循环变量内建函数</link>。</para>
-
- <para>配置设置项影响来自 <literal>Configuration</literal>
- 对象表达式解析(比如语法),而不是来自调用 <literal>eval</literal> 的的模板。</para>
- </section>
-
- <section xml:id="ref_builtin_has_content">
- <title>has_content</title>
-
- <indexterm>
- <primary>has_content built-in</primary>
- </indexterm>
-
- <para>如果变量(不是Java的 <literal>null</literal>)
- 存在而且不是"空"就返回 <literal>true</literal>,否则返回
- <literal>false</literal>。"空”"含义靠具体的情形来决定。
- 它是直观的常识性概念。下面这些都是空:长度为0的字符串,
- 没有子变量的序列或哈希表,一个已经超过最后一项元素的集合。
- 如果值不是字符串,序列,哈希表或集合,如果它是数字,日期或布尔值
- (比如 <literal>0</literal> 和 <literal>false</literal> 是非空的),
- 那么它被认为是非空的,否则就是空的。注意当你的数据模型实现了多个模板模型接口,
- 你可能会得到不是预期的结果。然而,当你有疑问时你通常可以使用
- <literal>expr!?size > 0</literal> 或 <literal>expr!?length >
- 0</literal> 来代替 <literal>expr?has_content</literal>。</para>
-
- <para>这个函数是个特殊的函数,你可以使用像 <link
- linkend="dgui_template_exp_missing_default">默认值操作符</link>
- 那样的圆括号手法。也就是说,你可以编写
- <literal>product.color?has_content</literal> 和
- <literal>(product.color)?has_content</literal> 这样的代码。
- 第一个没有控制当 <literal>product</literal> 为空的情形,而第二个控制了。</para>
- </section>
-
- <section xml:id="ref_builtin_interpret">
- <title>interpret</title>
-
- <indexterm>
- <primary>interpret built-in</primary>
- </indexterm>
-
- <para>该内建函数解析字符串作为FTL模板,而且返回一个用户自定义指令,
- 也就是当应用于任意代码块中时,执行模板就像它当时 <link
- linkend="ref_directive_include"><literal>被包含</literal></link>
- 一样。例如:</para>
-
- <programlisting role="template"><#assign x=["a", "b", "c"]>
-<#assign templateSource = r"<#list x as y>${y}</#list>">
-<#-- Note: That r was needed so that the ${y} is not interpreted above -->
-<#assign inlineTemplate = templateSource?interpret>
-<@inlineTemplate /></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">abc</programlisting>
-
- <para>正如你看到的,<literal>inlineTemplate</literal> 是用户自定义指令,
- 也就是当被执行时,运行当时内容是是 <literal>templateSource</literal> 值的模板。</para>
-
- <para>配置设置项影响来自 <literal>Configuration</literal>
- 对象的解析(比如标签语法和命名转换),而不是来自调用
- <literal>interpret</literal> 的模板。
- 这也就以为着之前的自动探测标签语法或命名转换不会影响已解释模板的解析。
- 这也和 <link
- linkend="ref_directive_include"><literal>include</literal>
- 指令</link> 的工作方式一致。</para>
-
- <para>通过 <literal>interpret</literal> 创建的模板名称就是模板调用
- <literal>interpret</literal> 的名称,并加上
- <literal>"->anonymous_interpreted"</literal>。例如,
- 如果模板调用的内建函数是 <literal>"foo/bar.ftl"</literal>,
- 那么结果模板的名称就是 <literal>"foo/bar.ftl->anonymous_interpreted"</literal>。
- 因此,在已解释模板中的相对路径也就相对于该路径
- (也就是说,根路径就是 <literal>"foo"</literal>),
- 而且在已解释模板内部的错误将会指向这个生成的模板名称。</para>
-
- <para>要得到更多有用的错误消息,可以在 <literal>"->"</literal>
- 之后覆盖模板名称部分。例如,我们说 <literal>mailTemplateSource</literal>
- 来自于数据库表 <literal>mail_template</literal>,那么在错误时,
- 想得到包含数据库ID的失败模板的错误日志:</para>
-
- <programlisting role="template"><#assign inlineTemplate = [mailTemplateSource, "mail_templates id=${mailTemplateId}"]?interpret></programlisting>
-
- <para>正如你看到的,<literal>interpret</literal> 可以应用于有两项的序列,
- 这里的第一项是要解释的FTL字符串,第二项是在 <literal>"->"</literal>
- 之后使用的模板名称。</para>
- </section>
-
- <section xml:id="ref_builtin_isType">
- <title>is_...</title>
-
- <indexterm>
- <primary>is_... built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>type checking</primary>
- </indexterm>
-
- <para>这些内建函数用来检查变量的类型,然后根据类型返回
- <literal>true</literal> 或 <literal>false</literal>。
- 下面是 <literal>is_<replaceable>...</replaceable></literal> 内建函数列表:</para>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>内建函数</th>
-
- <th>如果值是 … 时返回 <literal>true</literal></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>is_string</literal></td>
-
- <td>字符串</td>
- </tr>
-
- <tr>
- <td><literal>is_number</literal></td>
-
- <td>数字</td>
- </tr>
-
- <tr>
- <td><literal>is_boolean</literal></td>
-
- <td>布尔值</td>
- </tr>
-
- <tr>
- <td><literal>is_date</literal></td>
-
- <td>不要使用它!使用 <literal>is_date_like</literal> 来代替,
- 它们是相同的。往后也许会修改它的含义为 <literal>date_only</literal>。</td>
- </tr>
-
- <tr>
- <td><literal>is_date_like</literal></td>
-
- <td>日期,也就似乎日期,时间或者日期-时间,
- 亦或者是未知精确类型的日期(从 FreeMarker 2.3.21 版本开始)</td>
- </tr>
-
- <tr>
- <td><literal>is_date_only</literal></td>
-
- <td>日期 (没有时间部分) (从 FreeMarker 2.3.21 版本开始)</td>
- </tr>
-
- <tr>
- <td><literal>is_time</literal></td>
-
- <td>时间 (没有年-月-日部分) (从 FreeMarker 2.3.21 版本开始)</td>
- </tr>
-
- <tr>
- <td><literal>is_datetime</literal></td>
-
- <td>日期-时间 (包含年-月-日和时间两者)</td>
- </tr>
-
- <tr>
- <td><literal>is_unknown_date_like</literal></td>
-
- <td>不清楚是日期或时间或日期-时间的日期</td>
- </tr>
-
- <tr>
- <td><literal>is_method</literal></td>
-
- <td>方法</td>
- </tr>
-
- <tr>
- <td><literal>is_transform</literal></td>
-
- <td>变换</td>
- </tr>
-
- <tr>
- <td><literal>is_macro</literal></td>
-
- <td>宏或函数(当然,由于历史问题,也对函数有效)</td>
- </tr>
-
- <tr>
- <td><literal>is_hash</literal></td>
-
- <td>哈希表 (包含扩展的哈希表)</td>
- </tr>
-
- <tr>
- <td><literal>is_hash_ex</literal></td>
-
- <td>扩展的哈希表 (支持 <literal>?keys</literal> 和
- <literal>?values</literal>)</td>
- </tr>
-
- <tr>
- <td><literal>is_sequence</literal></td>
-
- <td>序列</td>
- </tr>
-
- <tr>
- <td><literal>is_collection</literal></td>
-
- <td>集合 (包含扩展的集合)</td>
- </tr>
-
- <tr>
- <td><literal>is_collection_ex</literal></td>
-
- <td>扩展的集合 (支持
- <literal>?size</literal>)</td>
- </tr>
-
- <tr>
- <td><literal>is_enumerable</literal></td>
-
- <td>序列或集合</td>
- </tr>
-
- <tr>
- <td><literal>is_indexable</literal></td>
-
- <td>序列</td>
- </tr>
-
- <tr>
- <td><literal>is_directive</literal></td>
-
- <td>指令类型 (例如宏 <phrase
- role="forProgrammers">或
- <literal>TemplateDirectiveModel</literal>,
- <literal>TemplateTransformModel</literal>, 等...</phrase>),
- 或者函数 (由于历史问题)</td>
- </tr>
-
- <tr>
- <td><literal>is_node</literal></td>
-
- <td>结点</td>
- </tr>
- </tbody>
- </informaltable>
- </section>
-
- <section xml:id="ref_builtin_namespace">
- <title>namespace</title>
-
- <indexterm>
- <primary>namespace built-in</primary>
- </indexterm>
-
- <para>该内建函数返回和宏变量或函数变量关联的命名空间
- (也就是命名空间的"入口"哈希表)。你只能和宏和函数一起来用它。</para>
- </section>
-
- <section xml:id="ref_builtin_new">
- <title>new</title>
-
- <indexterm>
- <primary>instantiating variable</primary>
- </indexterm>
-
- <indexterm>
- <primary>new built-in</primary>
- </indexterm>
-
- <para>这是用来创建一个确定的 <literal>TemplateModel</literal>
- 实现变量的内建函数。</para>
-
- <para>在 <literal>?</literal> 的左边你可以指定一个字符串,
- 是 <literal>TemplateModel</literal> 实现类的完全限定名。
- 结果是调用构造方法生成一个方法变量,然后将新变量返回。</para>
-
- <para>比如:</para>
-
- <programlisting role="template"><#-- Creates an user-defined directive be calling the parameterless constructor of the class -->
-<#assign word_wrapp = "com.acmee.freemarker.WordWrapperDirective"?new()>
-<#-- Creates an user-defined directive be calling the constructor with one numerical argument -->
-<#assign word_wrapp_narrow = "com.acmee.freemarker.WordWrapperDirective"?new(40)></programlisting>
-
- <para>更多关于构造方法参数被包装和如何选择重载的构造方法信息,
- 请阅读: <xref
- linkend="pgui_misc_beanwrapper"/></para>
-
- <para>该内建函数可以是出于安全考虑的,
- 因为模板作者可以创建任意的Java对象,只要它们实现了
- <literal>TemplateModel</literal> 接口,然后来使用这些对象。
- 而且模板作者可以触发没有实现 <literal>TemplateModel</literal>
- 接口的类的静态初始化块。你可以(从 2.3.17版开始)使用
- <literal>Configuration.setNewBuiltinClassResolver(TemplateClassResolver)</literal>
- 或设置 <literal>new_builtin_class_resolver</literal> 来限制这个内建函数对类的访问。
- 参考Java API文档来获取详细信息。如果允许并不是很可靠的用户上传模板,
- 那么你一定要关注这个问题。</para>
- </section>
-
- <section xml:id="ref_builtin_numToDate">
- <title>number_to_date, number_to_time, number_to_datetime</title>
-
- <indexterm>
- <primary>type-casting</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting between types</primary>
- </indexterm>
-
- <indexterm>
- <primary>number_to_date built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>number_to_datetime built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>number_to_time built-in</primary>
- </indexterm>
-
- <indexterm>
- <primary>converting long to date</primary>
- </indexterm>
-
- <indexterm>
- <primary>long to date</primary>
- </indexterm>
-
- <para>它们被用来转换数字(通常是Java的 <literal>long</literal >类型)到日期,
- 时间或时间日期类型。这就使得它们和Java中的
- <literal>new java.util.Date(long)</literal> 是一致的。那也就是说,
- 现在数字可以被解释成毫秒数进行参数传递。数字可以是任意内容和任意类型,
- 只要它的值可以认为是 <literal>long</literal> 就行。如果数字不是完整的,
- 那么它就会根据"五入"原则进行进位。这个转换不是自动进行的。</para>
-
- <para>比如:</para>
-
- <programlisting role="template">${1305575275540?number_to_datetime}
-${1305575275540?number_to_date}
-${1305575275540?number_to_time}</programlisting>
-
- <para>将会输出这样的内容(基于当前的本地化设置和时区):</para>
-
- <programlisting role="output">May 16, 2011 3:47:55 PM
-May 16, 2011
-3:47:55 PM</programlisting>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="ref_directives">
- <title>指令参考</title>
-
- <indexterm>
- <primary>directive</primary>
- </indexterm>
-
- <section xml:id="ref_directive_alphaidx">
- <title>Alphabetical index</title>
-
- <note>
- <para>基于 FreeMarker 2.3.23,对指令名可以使用驼峰形式来代替全小写形式,
- 比如 <literal>noParse</literal> 来代替 <literal>noparse</literal>。
- 但是要知道在相同的模板中,FreeMarker 对所有标识符会强制使用驼峰形式,
- 这是模板语言的一部分(用户自定义名称不受影响)。</para>
- </note>
-
- <indexterm>
- <primary>directive</primary>
- </indexterm>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><link linkend="ref.directive.assign">assign</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.attempt">attempt</link></para>
- </listitem>
-
- <listitem>
- <para>break: <link linkend="ref.directive.switch.break">in
- switch</link>, <link linkend="ref.directive.list.break">in
- list</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.case">case</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.compress">compress</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.default">default</link></para>
- </listitem>
-
- <listitem>
- <para>else: <link linkend="ref.directive.else">in if</link>, <link
- linkend="ref.directive.list.else">in list</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.elseif">elseif</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.escape">escape</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.fallback">fallback</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.function">function</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.flush">flush</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.ftl">ftl</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.global">global</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.if">if</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.import">import</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.include">include</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.items">items</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.list">list</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.local">local</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.lt">lt</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.macro">macro</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.nested">nested</link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.noescape">noescape</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_directive_noparse">noparse</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.nt">nt</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.attempt">recover</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.recurse">recurse</link></para>
- </listitem>
-
- <listitem>
- <para>return: <link linkend="ref.directive.macro.return">in
- macro</link>, <link linkend="ref.directive.function.return">in
- function</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.rt">rt</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.sep">sep</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.setting">setting</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.stop">stop</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.switch">switch</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.t">t</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.userDefined">User-defined
- directive (<@...>)</link></para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref.directive.visit">visit</link></para>
- </listitem>
- </itemizedlist>
-
- <para>如果在这里没有发现模板中使用的指令,那么可能会在 <xref
- linkend="ref_deprecated"/> 中找到。</para>
- </section>
-
- <section xml:id="ref_directive_assign">
- <title>assign</title>
-
- <anchor xml:id="ref.directive.assign"/>
-
- <indexterm>
- <primary>assign directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#assign <replaceable>name1</replaceable>=<replaceable>value1</replaceable> <replaceable>name2</replaceable>=<replaceable>value2</replaceable> <replaceable>... nameN</replaceable>=<replaceable>valueN</replaceable>></literal>
-或
-<literal><#assign <replaceable>same as above...</replaceable> in <replaceable>namespacehash</replaceable>></literal>
-或
-<literal><#assign <replaceable>name</replaceable>>
- <replaceable>capture this</replaceable>
-</#assign></literal>
-或
-<literal><#assign <replaceable>name</replaceable> in <replaceable>namespacehash</replaceable>>
- <replaceable>capture this</replaceable>
-</#assign></literal></programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:变量的名字。
- 它不是表达式。而它可以写作是字符串,如果变量名包含保留字符这是很有用的,
- 比如 <literal><#assign "foo-bar" = 1></literal>。
- 请注意这个字符串没有展开插值(如<literal>"${foo}"</literal>);
- 如果需要赋值一个动态创建的名字,那么不得不使用 <link
- linkend="faq_assign_to_dynamic_variable_name">这个技巧</link>。</para>
- </listitem>
-
- <listitem>
- <para><literal>=</literal>:赋值操作符。
- 它也可以是一个简写的赋值操作符(从 FreeMarker 2.3.23 版本开始):
- <literal>++</literal>,<literal>--</literal>,
- <literal>+=</literal>,<literal>-=</literal>,
- <literal>*=</literal>,<literal>/=</literal> 或
- <literal>%=</literal>。比如 <literal><#assign
- x++></literal> 和 <literal><#assign x = x +
- 1></literal> 是一样的,并且 <literal><#assign x += 2></literal>
- 和 <literal><#assign x = x + 2></literal> 是相同的。
- 请注意, <literal>++</literal> 通常意味着算术加法
- (对于非数字将会失败),不像
- <literal>+</literal> 或 <literal>+=</literal>
- 可以进行字符连接等重载操作。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value</replaceable></literal>:
- 存储的值。是表达式。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>namespacehash</replaceable></literal>:(通过 <link
- linkend="ref.directive.import"><literal>import</literal></link>)
- 为命名空间创建的哈希表。是表达式。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>使用该指令你可以创建一个新的变量,
- 或者替换一个已经存在的变量。注意仅仅顶级变量可以被创建/替换
- (也就是说你不能创建/替换 <literal>some_hash.subvar</literal>,
- 除了 <literal>some_hash</literal>)。</para>
-
- <para>关于变量的更多内容,请阅读:<xref
- linkend="dgui_misc_var"/></para>
-
- <para>比如:变量 <literal>seq</literal> 存储一个序列:</para>
-
- <programlisting role="template"><#assign seq = ["foo", "bar", "baz"]></programlisting>
-
- <para>比如:变量 <literal>x</literal> 中存储增长的数字:</para>
-
- <programlisting role="template"><#assign x++></programlisting>
-
- <para>作为一个方便的特性,你可以使用一个 <literal>assign</literal>
- 标记来进行多次定义。比如这个会做上面两个例子中相同的事情:</para>
-
- <programlisting role="template"><#assign
- seq = ["foo", "bar", "baz"]
- x++
-></programlisting>
-
- <para>如果你知道什么是命名空间:<literal>assign</literal>
- 指令在命名空间中创建变量。通常它在当前的命名空间
- (也就是和标签所在模板关联的命名空间)中创建变量。但如果你是用了
- <literal>in <replaceable>namespacehash</replaceable></literal>,
- 那么你可以用另外一个 <link
- linkend="dgui_misc_namespace">命名空间</link> 来创建/替换变量。
- 比如,这里你在命名空间中 <literal>/mylib.ftl</literal>
- 创建/替换了变量 <literal>bgColor</literal>:</para>
-
- <programlisting role="template"><#import "/mylib.ftl" as my>
-<#assign bgColor="red" in my></programlisting>
-
- <para> <literal>assign</literal>
- 的极端使用是当它捕捉它的开始标记和结束标记中间生成的输出时。
- 也就是说,在标记之间打印的东西将不会在页面上显示,
- 但是会存储在变量中。比如:</para>
-
- <programlisting role="template"><#macro myMacro>foo</#macro>
-<#assign x>
- <#list 1..3 as n>
- ${n} <@myMacro />
- </#list>
-</#assign>
-Number of words: ${x?word_list?size}
-${x}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Number of words: 6
- 1 foo
- 2 foo
- 3 foo
- </programlisting>
-
- <para>请注意,你不应该使用它来往字符串中插入变量:</para>
-
- <programlisting role="template"><#assign x>Hello ${user}!</#assign> <#-- BAD PRACTICE! --></programlisting>
-
- <para>你可以这么来写:</para>
-
- <programlisting role="template"><#assign x="Hello ${user}!"></programlisting>
- </section>
- </section>
-
- <section xml:id="ref_directive_attempt">
- <title>attempt, recover</title>
-
- <anchor xml:id="ref.directive.attempt"/>
-
- <indexterm>
- <primary>attempt directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>recover directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>error handling</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#attempt>
- <replaceable>attempt block</replaceable>
-<#recover>
- <replaceable>recover block</replaceable>
-</#attempt>
-</literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>attempt
- block</replaceable></literal>:任意内容的模板块。这是会被执行的,
- 但是如果期间发生了错误,那么这块内容的输出将会回滚,
- 之后 <literal><replaceable>recover block</replaceable></literal>
- 就会被执行。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>recover
- block</replaceable></literal>: 任意内容的模板块。
- 这个仅在 <literal><replaceable>attempt block</replaceable></literal>
- 执行期间发生错误时被执行。你可以在这里打印错误信息或其他操作。</para>
- </listitem>
- </itemizedlist>
-
- <para><literal><replaceable>recover</replaceable></literal> 是强制的。
- <literal>attempt</literal>/<literal>recover</literal> 可以嵌套在其他
- <literal><replaceable>attempt block</replaceable></literal> 或
- <literal><replaceable>recover block</replaceable></literal>中。</para>
-
- <note>
- <para>上面的格式是从 2.3.3 版本开始支持的,之前它是
- <literal><#attempt><replaceable>...</replaceable><#recover><replaceable>...</replaceable></#recover></literal>,也支持向下兼容。此外,
- 这些指令是在 FreeMarker 2.3.1 版本时引入的,在 2.3 版本中是不存在的。</para>
- </note>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>如果你想让页面成功输出内容,尽管它在页面特定位置发生错误也这样,
- 那么这些指令就是有用的。如果一个错误在
- <literal><replaceable>attempt block</replaceable></literal> 执行期间发生,
- 那么模板执行就会中止,但是 <literal><replaceable>recover block</replaceable></literal>
- 会代替 <literal><replaceable>attempt block</replaceable></literal> 执行。
- 如果在 <literal><replaceable>attempt block</replaceable></literal> 执行期间没有发生错误,
- 那么 <literal><replaceable>recover block</replaceable></literal> 就会忽略。
- 一个简单的示例如下:</para>
-
- <programlisting role="template">Primary content
-<#attempt>
- Optional content: ${thisMayFails}
-<#recover>
- Ops! The optional content is not available.
-</#attempt>
-Primary content continued</programlisting>
-
- <para>如果 <literal>thisMayFails</literal> 变量不存在,将会输出:</para>
-
- <programlisting role="output">Primary content
- Ops! The optional content is not available.
-Primary content continued</programlisting>
-
- <para>如果 <literal>thisMayFails</literal> 变量存在而且值为
- <literal>123</literal>,将会输出:</para>
-
- <programlisting role="output">Primary content
- Optional content: 123
-Primary content continued</programlisting>
-
- <para><literal><replaceable>attempt block</replaceable></literal>
- 块有多或没有的语义:不管 <literal><replaceable>attempt
- block</replaceable></literal>块的完整内容是否输出(没有发生错误),
- 或者在 <literal><replaceable>attempt block</replaceable></literal>
- (没有发生错误)块执行时没有输出结果。比如,上面的示例,
- 发生在"Optional content"之后的失败被打印出来了,而没有在"Ops!"之前输出。
- (<phrase role="forProgrammers">这是在
- <literal><replaceable>attempt block</replaceable></literal>
- 块内,侵入的输出缓冲的实现,甚至连 <literal>flush</literal>
- 指令也会送输出到客户端。</phrase>)</para>
-
- <para>为了阻止来自上面示例的误解:
- <literal>attempt</literal>/<literal>recover</literal>
- 不(仅仅)是处理未定义变量(可以使用 <link
- linkend="dgui_template_exp_missing">不存在变量控制符</link>)。
- 它可以处理发生在块执行期间的各种类型的错误
- (而不是语法错误,这会在执行之前被检测到)。它的目的是包围更大的模板段,
- 错误可能发生在很多地方。比如,你在模板中有一个部分,来处理打印广告,
- 但是它不是页面的主要内容,所以你不想你的页面因为一些打印广告
- (也可能是短暂的数据库服务器故障)的错误而挂掉。
- 所以你将整个广告区域放在 <literal><replaceable>attempt
- block</replaceable></literal> 块中。</para>
-
- <para>在一些环境下,程序员配置 FreeMarker,所以对于特定的错误,
- 它不会中止模板的执行,在打印一些错误提示信息到输出
- (<phrase role="forProgrammers">更多内容,请参考 <link
- linkend="pgui_config_errorhandling">这里...</link></phrase>)中之后,
- 而是继续执行。<literal>attempt</literal> 指令不会将这些抑制的错误视为错误。</para>
-
- <para>在 <literal><replaceable>recover block</replaceable></literal>
- 块中,错误的信息存在 <link
- linkend="ref_specvar">特殊变量</link> <literal>error</literal> 中。
- 不要忘了以点开始引用特殊变量(比如:<literal>${.error}</literal>)。</para>
-
- <para><phrase role="forProgrammers">在模板执行期间发生的错误通常被 <link
- linkend="pgui_misc_logging">日志记录</link>,不管是否发生在
- <literal><replaceable>attempt
- block</replaceable></literal>块中。</phrase></para>
- </section>
- </section>
-
- <section xml:id="ref_directive_compress">
- <title>compress</title>
-
- <anchor xml:id="ref.directive.compress"/>
-
- <indexterm>
- <primary>compress directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>compress</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#compress>
- <replaceable>...</replaceable>
-</#compress></literal>
-</programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>当你使用了对空白不敏感的格式(比如HTML或XML)
- 时压缩指令对于移除多余的 <link linkend="gloss.whiteSpace">空白</link>
- 是很有用的。它捕捉在指令体(也就是在开始标签和结束标签中)中生成的内容,
- 然后缩小所有不间断的空白序列到一个单独的空白字符。
- 如果被替代的序列包含换行符或是一段空间,那么被插入的字符也会是一个 <link
- linkend="gloss.lineBreak">换行符</link>。
- 开头和结尾的不间断的空白序列将会完全被移除。</para>
-
- <programlisting role="template"><#assign x = " moo \n\n ">
-(<#compress>
- 1 2 3 4 5
- ${moo}
- test only
-
- I said, test only
-
-</#compress>)</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">(1 2 3 4 5
-moo
-test only
-I said, test only)</programlisting>
- </section>
- </section>
-
- <section xml:id="ref_directive_escape">
- <title>escape, noescape</title>
-
- <anchor xml:id="ref.directive.escape"/>
-
- <anchor xml:id="ref.directive.noescape"/>
-
- <indexterm>
- <primary>escape directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>noescape directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#escape <replaceable>identifier</replaceable> as <replaceable>expression</replaceable>>
- <replaceable>...</replaceable>
- <#noescape><replaceable>...</replaceable></#noescape>
- <replaceable>...</replaceable>
-</#escape></literal>
-</programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>当你使用escape指令包围模板中的一部分时,在块中出现的插值
- (<literal>${<replaceable>...</replaceable>}</literal>)
- 会和转义表达式自动结合。这是一个避免编写相似表达式的很方便的方法。
- 它不会影响在字符串形式的插值(比如在 <literal><#assign x =
- "Hello ${user}!"></literal>)。而且,它也不会影响数值插值
- (<literal>#{<replaceable>...</replaceable>}</literal>)。</para>
-
- <para>例如:</para>
-
- <programlisting role="template"><emphasis><#escape x as x?html></emphasis>
- First name: ${firstName}
- Last name: ${lastName}
- Maiden name: ${maidenName}
-<emphasis></#escape></emphasis></programlisting>
-
- <para>事实上它等同于:</para>
-
- <programlisting role="template"> First name: ${firstName<emphasis>?html</emphasis>}
- Last name: ${lastName<emphasis>?html</emphasis>}
- Maiden name: ${maidenName<emphasis>?html</emphasis>}</programlisting>
-
- <para>请注意,它和你在指令中用什么样的标识符无关 -
- 它仅仅是作为一个转义表达式的正式参数。</para>
-
- <para>当在调用宏或者 <literal>include</literal> 指令时,
- 理解 <emphasis>在模板文本</emphasis> 中转义仅仅对出现在 <literal><#escape
- <replaceable>...</replaceable>></literal> 和
- <literal></#escape></literal> 中的插值起作用是很重要的。
- 也就是说,它不会转义文本中
- <literal><#escape <replaceable>...</replaceable>></literal>
- 之前的东西或 <literal></#escape></literal> 之后的东西,
- 也不会从 <literal>escape</literal> 的部分中来调用。</para>
-
- <programlisting role="template"><#assign x = "<test>">
-<#macro m1>
- m1: ${x}
-</#macro>
-<#escape x as x?html>
- <#macro m2>m2: ${x}</#macro>
- ${x}
- <@m1/>
-</#escape>
-${x}
-<@m2/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> &lt;test&gt;
- m1: <test>
-<test>
-m2: &lt;test&gt;</programlisting>
-
- <para><phrase role="forProgrammers">从更深的技术上说,
- <literal>escape</literal> 指令的作用是用在模板解析的时间而不是模板处理的时间。
- 这就表示如果你调用一个宏或从一个转义块中包含另外一个模板,
- 它不会影响在宏/被包含模板中的插值,因为宏调用和模板包含被算在模板处理时间。
- 另外一方面,如果你用一个转义区块包围一个或多个宏声明(算在模板解析时间,和宏调用想法),
- 那么那些宏中的插值将会和转义表达式合并。</phrase></para>
-
- <para>有时需要暂时为一个或两个在转义区块中的插值关闭转义。你可以通过关闭,
- 过后再重新开启转义区块来达到这个功能,但是那么你不得不编写两遍转义表达式。
- 你可以使用非转义指令来替代:</para>
-
- <programlisting role="template"><#escape x as x?html>
- From: ${mailMessage.From}
- Subject: ${mailMessage.Subject}
- <emphasis><#noescape></emphasis>Message: ${mailMessage.htmlFormattedBody}<emphasis></#noescape></emphasis>
- <replaceable>...</replaceable>
-</#escape></programlisting>
-
- <para>和这个是等同的:</para>
-
- <programlisting role="template"> From: ${mailMessage.From?html}
- Subject: ${mailMessage.Subject?html}
- Message: ${mailMessage.htmlFormattedBody}
- ...</programlisting>
-
- <para>转义可以被嵌套(尽管你不会在罕见的情况下来做)。
- 因此,你可以编写如下面代码(这个例子固然是有点伸展的,
- 正如你可能会使用 <literal>list</literal> 来迭代序列中的每一项,
- 但是我们现在所做的是阐述这个观点)的东西:</para>
-
- <programlisting role="template"><emphasis><#escape x as x?html></emphasis>
- Customer Name: ${customerName}
- Items to ship:
- <emphasis><#escape x as itemCodeToNameMap[x]></emphasis>
- ${itemCode1}
- ${itemCode2}
- ${itemCode3}
- ${itemCode4}
- <emphasis></#escape></emphasis>
-<emphasis></#escape></emphasis></programlisting>
-
- <para>实际上和下面是等同的:</para>
-
- <programlisting role="template"> Customer Name: ${customerName?html}
- Items to ship:
- ${itemCodeToNameMap[itemCode1]?html}
- ${itemCodeToNameMap[itemCode2]?html}
- ${itemCodeToNameMap[itemCode3]?html}
- ${itemCodeToNameMap[itemCode4]?html}</programlisting>
-
- <para>当你在嵌入的转义区块内使用非转义指令时,它仅仅不处理一个单独层级的转义。
- 因此,为了在两级深的转义区块内完全关闭转义,你需要使用两个嵌套的非转义指令。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_flush">
- <title>flush</title>
-
- <anchor xml:id="ref.directive.flush"/>
-
- <indexterm>
- <primary>flush directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#flush></literal></programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>当 FreeMarker 生成输出时,它通常不会立即发送到最终接收端
- (比如web浏览器或最终的文件),而是会将内容累积在缓冲区,发送一个大块的内容。
- 缓冲区的精确规则不是由 FreeMarker 决定的,而是由嵌入的软件决定的。
- 将缓冲区中累积的内容发送出去称为冲洗。尽管冲洗是自动发生的,
- 有时你想在模板处理时的一点强制执行,这就是 <literal>flush</literal>
- 指令要做的。如果需要在确定之处用到它,这是由程序员决定的,而不是设计师。</para>
-
- <para>请注意, <literal>flush</literal> 告诉嵌入的软件我们想要冲洗,
- 那么也许就会决定忽略该请求。这不由 FreeMarker 之手控制。</para>
-
- <para><phrase role="forProgrammers">冲洗简单调用当前使用
- <literal>java.io.Writer</literal> 实例的 <literal>flush()</literal> 方法。
- 整个缓冲区和冲洗机制由 <literal>Writer</literal>(就是传递给
- <literal>Template.process</literal> 方法的参数)实现;
- FreeMarker不用来处理它。</phrase></para>
- </section>
- </section>
-
- <section xml:id="ref_directive_ftl">
- <title>ftl</title>
-
- <anchor xml:id="ref.directive.ftl"/>
-
- <indexterm>
- <primary>ftl directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>header</primary>
- </indexterm>
-
- <indexterm>
- <primary>charset</primary>
- </indexterm>
-
- <indexterm>
- <primary>encoding</primary>
- </indexterm>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>stripping</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#ftl <replaceable>param1</replaceable>=<replaceable>value1</replaceable> <replaceable>param2</replaceable>=<replaceable>value2</replaceable> <replaceable>...</replaceable> <replaceable>paramN</replaceable>=<replaceable>valueN</replaceable>></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>param1</replaceable></literal>,
- <literal><replaceable>param2</replaceable></literal> 等:
- 参数的名字,不是表达式。允许的参数有:
- <literal>encoding</literal>,
- <literal>strip_whitespace</literal>,
- <literal>strip_text</literal> 等。参见下面。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value1</replaceable></literal>,
- <literal><replaceable>value2</replaceable></literal> 等:
- 参数的值。必须是一个常量表达式(如 <literal>true</literal>,
- 或 <literal>"ISO-8859-5"</literal>,或
- <literal>{x:1, y:2}</literal>)。它不能用变量。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>告诉 FreeMarker 和其他工具关于模板的信息,
- 而且帮助程序员来自动检测一个文本文件是否是 FTL 文件。该指令,
- 如果存在,必须是模板的第一句代码。该指令前的任何 <link
- linkend="gloss.whiteSpace">空白</link> 将被忽略。
- 该指令的老式语法(<literal>#</literal>-less)格式是不支持的。</para>
-
- <para>一些设置(编码方式,空白剥离等)在这里给定的话就有最高的优先级,
- 也就是说,它们直接作用于模板而不管其他任何 FreeMarker 的配置设置。</para>
-
- <para>参数:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>encoding</literal>:
- 使用它,你可以在模板文件本身中为模板指定编码方式(字符集)
- <phrase role="forProgrammers">(也就是说,这是新创建
- <literal>Template</literal> 的 <literal>encoding</literal> 设置,
- 而且 <literal>Configuration.getTemplate</literal> 中的
- <literal>encoding</literal> 参数不能覆盖它</phrase>)。要注意,
- FreeMarker 会尝试会和自动猜测的编码方式
- (这依赖于程序员对 FreeMarker 的配置)找到 <literal>ftl</literal>
- 指令并解释它,然后就会发现 <literal>ftl</literal>
- 指令会让一些东西有所不同,之后以新的编码方式来重新读取模板。
- 因此,直到 <literal>ftl</literal> 标记使用第一个编码方式读取到结尾,
- 模板必须是合法的FTL。
- 这个参数的合法值是从IANA字符集注册表中参考MIME中的字符集名称。
- 比如 ISO-8859-5,UTF-8 或 Shift_JIS。</para>
- </listitem>
-
- <listitem>
- <para><literal>strip_whitespace</literal>:
- 这将开启/关闭 <link linkend="dgui_misc_whitespace_stripping">空白剥离</link>。
- 合法的值是布尔值常量 <literal>true</literal> 和 <literal>false</literal>
- (为了向下兼容,字符串 <literal>"yes"</literal>,<literal>"no"</literal>,
- <literal>"true"</literal>,<literal>"false"</literal> 也是可以的)。
- 默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker 的配置,
- 但是对新的项目还应该是 <literal>true</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>strip_text</literal>:当开启它时,
- 当模板被解析时模板中所有顶级文本被移除。这个不会影响宏,指令,或插值中的文本。
- 合法值是布尔值常量 <literal>true</literal> 和 <literal>false</literal>
- (为了向下兼容,字符串 <literal>"yes"</literal>,<literal>"no"</literal>,
- <literal>"true"</literal>,<literal>"false"</literal> 也是可以的)。
- 默认值(也就是当你不使用这个参数时)是 <literal>false</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>strict_syntax</literal>:这会开启/关闭"严格的语法"。
- 合法值是布尔值常量 <literal>true</literal> 和 <literal>false</literal>
- (为了向下兼容,字符串 <literal>"yes"</literal>,<literal>"no"</literal>,
- <literal>"true"</literal>,<literal>"false"</literal> 也是可以的)。
- 默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker 的配置,
- 但是对新的项目还应该是 <literal>true</literal>。<phrase
- role="forProgrammers">(程序员:对于
- <literal>config.setStrictSyntaxMode(true);</literal> 你必须明确设置它为
- <literal>true</literal>)</phrase> 要获取更多信息,可以参考:<xref
- linkend="ref_depr_oldsyntax"/></para>
- </listitem>
-
- <listitem>
- <para><literal>ns_prefixes</literal>:这是关联结点命名空间前缀的哈希表。
- 比如:<literal>{"e":"http://example.com/ebook",
- "vg":"http://example.com/vektorGraphics"}</literal>。这通常是用于XML处理的,
- 前缀可以用于XML查询,但是它也影响 <link
- linkend="ref_directive_visit">指令
- <literal>visit</literal> 和 <literal>recurse</literal></link>的工作。
- 相同结点的命名空间只能注册一个前缀(否则会发生错误),
- 所以在前缀和结点命名空间中是一对一的关系。前缀 <literal>D</literal>
- 和 <literal>N</literal> 是保留的。如果你注册前缀 <literal>D</literal>,
- 那么除了你可以使用前缀 <literal>D</literal> 来关联结点命名空间,
- 你也可以设置默认的结点命名空间。前缀 <literal>N</literal> 不能被注册;
- 当且仅当前缀 <literal>D</literal> 被注册时,
- 它被用来表示在特定位置没有结点命名空间的结点。
- (要参考默认结点命名空间的用法,<literal>N</literal>,一般的前缀,
- 可以参考 <link linkend="xgui">XML
- 处理</link> 和 <link linkend="ref_directive_visit"><literal>visit</literal> 和
- <literal>recurse</literal></link>)。<literal>ns_prefixes</literal>
- 的作用限制在单独的 <link linkend="dgui_misc_namespace">FTL 命名空间</link>内,
- 换句话说,就是为模板创建的FTL命名空间内。这也意味着
- <literal>ns_prefixes</literal> 当一个FTL命名空间为包含它的模板所创建时才有作用,
- 否则 <literal>ns_prefixes</literal> 参数没有效果。FTL命名空间当下列情况下为模板创建:
- (a)模板是"主"模板,也就是说它不是被 <literal><#include ...></literal>
- 来调用的模板,但是直接被调用的(<phrase role="forProgrammers">和类
- <literal>Template</literal> 或
- <literal>Environment</literal> 中的Java方法 <literal>process</literal></phrase>);
- (b)模板直接被 <literal><#import ...></literal> 调用。</para>
- </listitem>
-
- <listitem>
- <para><literal>attributes</literal>:这是关联模板任意属性(名-值对)的哈希表。
- 属性的值可以是任意类型(字符串,数字,序列等)。FreeMarker 不会尝试去理解属性的含义。
- 它是由封装 FreeMarker(比如Web应用框架)的应用程序决定的。
- 因此,允许的属性的设置是它们依赖的应用(Web应用框架)的语义。
- <phrase role="forProgrammers">程序员:你可以通过关联
- <literal>Template</literal> 对象的
- <literal>getCustomAttributeNames</literal> 和
- <literal>getCustomAttribute</literal> 方法
- (从 <literal>freemarker.core.Configurable</literal> 继承而来)获得属性。
- 如当模板被解析时,关联 <literal>Template</literal> 对象的模板属性,
- 属性可以在任意时间被读取,而模板不需要被执行。
- 上面提到的方法返回未包装的属性值,也就是说,
- 使用 FreeMarker 独立的类型,如 <literal>java.util.List</literal>。</phrase></para>
- </listitem>
- </itemizedlist>
-
- <para>该指令也决定模板是否使用尖括号语法(比如
- <literal><#include 'foo.ftl'></literal>)或 <link
- linkend="dgui_misc_alternativesyntax">方括号语法</link>
- (如 <literal>[#include 'foo.ftl']</literal>)。简单而言,
- 该指令使用的语法将会是整个模板使用的语法,
- 而不管 FreeMarker 是如何配置的。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_function">
- <title>function, return</title>
-
- <anchor xml:id="ref.directive.function"/>
-
- <anchor xml:id="ref.directive.function.return"/>
-
- <indexterm>
- <primary>function directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>return directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>method</primary>
-
- <secondary>defining with FTL</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#function <replaceable>name</replaceable> <replaceable>param1</replaceable> <replaceable>param2</replaceable> <replaceable>... paramN</replaceable>>
- <replaceable>...</replaceable>
- <#return <replaceable>returnValue</replaceable>>
- <replaceable>...</replaceable>
-</#function></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:方法变量的名称(不是表达式)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>param1</replaceable></literal>,
- <literal><replaceable>param2</replaceable></literal> 等:
- <link linkend="dgui_misc_var">局部变量</link>的名称,
- 存储参数的值(不是表达式),在 <literal>=</literal> 号后面和默认值
- (是表达式)是可选的。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>paramN</replaceable></literal>,最后一个参数,
- 可以可选的包含一个尾部省略(<literal>...</literal>),
- 这就意味着宏接受可变的参数数量。局部变量
- <literal><replaceable>paramN</replaceable></literal> 将是额外参数的序列。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>returnValue</replaceable></literal>:
- 计算方法调用值的表达式。</para>
- </listitem>
- </itemizedlist>
-
- <para><literal>return</literal> 指令可以在 <literal><#function
- <replaceable>...</replaceable>></literal> 和
- <literal></#function></literal> 之间被用在任意位置和任意次数。</para>
-
- <para>没有默认值的参数必须在有默认值参数
- (<literal><replaceable>paramName</replaceable>=<replaceable>defaultValue</replaceable></literal>)
- 之前</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>创建一个方法变量(在当前命名空间中,如果你知道命名空间的特性)。
- 这个指令和 <link linkend="ref.directive.macro"><literal>macro</literal>
- 指令</link> 的工作方式一样,除了 <literal>return</literal>
- 指令必须有一个参数来指定方法的返回值,而且视图写入输出的将会被忽略。
- 如果到达 <literal></#function></literal>
- (也就是说没有 <literal>return
- <replaceable>returnValue</replaceable></literal>),
- 那么方法的返回值就是未定义变量。</para>
-
- <para>示例1:创建一个方法来计算两个数的平均值:</para>
-
- <programlisting role="template"><#function avg x y>
- <#return (x + y) / 2>
-</#function>
-${avg(10, 20)}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">15</programlisting>
-
- <para>示例2:创建一个方法来计算多个数的平均值:</para>
-
- <programlisting role="template"><#function avg nums...>
- <#local sum = 0>
- <#list nums as num>
- <#local sum = sum + num>
- </#list>
- <#if nums?size != 0>
- <#return sum / nums?size>
- </#if>
-</#function>
-${avg(10, 20)}
-${avg(10, 20, 30, 40)}
-${avg()!"N/A"}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">15
-25
-N/A</programlisting>
- </section>
- </section>
-
- <section xml:id="ref_directive_global">
- <title>global</title>
-
- <anchor xml:id="ref.directive.global"/>
-
- <indexterm>
- <primary>global directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#global <replaceable>name</replaceable>=<replaceable>value</replaceable>></literal>
-或
-<literal><#global <replaceable>name1</replaceable>=<replaceable>value1</replaceable> <replaceable>name2</replaceable>=<replaceable>value2</replaceable> <replaceable>... nameN</replaceable>=<replaceable>valueN</replaceable>></literal>
-或
-<literal><#global <replaceable>name</replaceable>>
- <replaceable>capture this</replaceable>
-</#global></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:变量的名称。
- 它不是表达式。但它可以被写作是字符串形式,如果变量名包含保留字符这是很有用的,
- 比如 <literal><#global "foo-bar" = 1></literal>。
- 注意这个字符串没有扩展插值(如 <literal>"${foo}"</literal>)。</para>
- </listitem>
-
- <listitem>
- <para><literal>=</literal>:赋值操作符,也可以简写的赋值操作符之一
- (<literal>++</literal>, <literal>+=</literal>,等...),和
- <link linkend="ref_directive_assign">
- <literal>assign</literal> 指令</link> 相似</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value</replaceable></literal>:存储的值,是表达式。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>该指令和 <link
- linkend="ref.directive.assign"><literal>assign</literal></link> 相似,
- 但是被创建的变量在所有的 <link
- linkend="dgui_misc_namespace">命名空间</link> 中都可见,
- 但又不会存在于任何一个命名空间之中。精确地说,正如你会创建
- (或替换)一个数据模型变量。因此,这个变量是全局的。如果在数据模型中,
- 一个相同名称的变量存在的话,它会被使用该指令创建的变量隐藏。
- 如果在当前的命名空间中,一个相同名称的变量存在的话,
- 那么会隐藏由 <literal>global</literal> 指令创建的变量。</para>
-
- <para>例如,用 <literal><#global x = 1></literal> 创建一个变量,
- 那么在所有命名空间中 <literal>x</literal> 都可见,
- 除非另外一个称为 <literal>x</literal> 的变量隐藏了它
- (比如你已经用 <literal><#assign x = 2></literal> 创建了一个变量)。
- 这种情形下,你可以使用 <link linkend="dgui_template_exp_var_special">特殊变量</link>
- <literal>globals</literal>,比如 <literal>${.globals.x}</literal>。请注意,
- 使用 <literal>globals</literal> 你看到所有全局可访问的变量;
- 不但由 <literal>global</literal> 指令创建的变量,而且是数据模型中的变量。</para>
-
- <para>自定义JSP标记的用户请注意:用这个指令创建的变量集合和JSP页面范围对应。
- 这就意味着,如果自定义JSP标记想获得一个页面范围的属性(page-scope bean),
- 在当前命名空间中一个相同名称的变量,从JSP标记的观点出发,将不会隐藏。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_if">
- <title>if, else, elseif</title>
-
- <anchor xml:id="ref.directive.if"/>
-
- <anchor xml:id="ref.directive.else"/>
-
- <anchor xml:id="ref.directive.elseif"/>
-
- <indexterm>
- <primary>if directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>else directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>elseif directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#if <replaceable>condition</replaceable>>
- <replaceable>...</replaceable>
-<#elseif <replaceable>condition2</replaceable>>
- <replaceable>...</replaceable>
-<#elseif <replaceable>condition3</replaceable>>
- <replaceable>...</replaceable>
-<replaceable>...</replaceable>
-<#else>
- <replaceable>...</replaceable>
-</#if></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>condition</replaceable></literal>,
- <literal><replaceable>condition2</replaceable></literal>,
- 等:将被计算成布尔值的表达式。</para>
- </listitem>
- </itemizedlist>
-
- <para><literal>elseif</literal> 和
- <literal>else</literal> 是可选的。</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>你可以使用 <literal>if</literal>, <literal>elseif</literal>
- 和 <literal>else</literal> 指令来条件判断是否越过模板的一个部分。
- <literal><replaceable>condition</replaceable></literal> 必须计算成布尔值,
- 否则错误将会中止模板处理。<literal>elseif</literal> 和
- <literal>else</literal> 必须出现在 <literal>if</literal> 内部
- (也就是,在 <literal>if</literal> 的开始标签和结束标签之间)。
- <literal>if</literal> 中可以包含任意数量的
- <literal>elseif</literal>(包括0个) 而且结束时
- <literal>else</literal> 是可选的。比如:</para>
-
- <para>只有 <literal>if</literal> 没有 <literal>elseif</literal> 和
- <literal>else</literal>:</para>
-
- <programlisting role="template"><#if x == 1>
- x is 1
-</#if></programlisting>
-
- <para>只有 <literal>if</literal> 没有 <literal>elseif</literal> 但是有
- <literal>else</literal>:</para>
-
- <programlisting role="template"><#if x == 1>
- x is 1
-<#else>
- x is not 1
-</#if></programlisting>
-
- <para>有 <literal>if</literal> 和两个 <literal>elseif</literal> 但是没有
- <literal>else</literal>:</para>
-
- <programlisting role="template"><#if x == 1>
- x is 1
-<#elseif x == 2>
- x is 2
-<#elseif x == 3>
- x is 3
-</#if></programlisting>
-
- <para>有 <literal>if</literal> 和三个 <literal>elseif</literal> 还有
- <literal>else</literal>:</para>
-
- <programlisting role="template"><#if x == 1>
- x is 1
-<#elseif x == 2>
- x is 2
-<#elseif x == 3>
- x is 3
-<#elseif x == 4>
- x is 4
-<#else>
- x is not 1 nor 2 nor 3 nor 4
-</#if></programlisting>
-
- <para>要了解更多布尔表达式,可以参考:<xref
- linkend="dgui_template_exp"/>.</para>
-
- <para>你(当然)也可以嵌套 <literal>if</literal> 指令:</para>
-
- <programlisting role="template"><#if x == 1>
- x is 1
- <#if y == 1>
- and y is 1 too
- <#else>
- but y is not
- </#if>
-<#else>
- x is not 1
- <#if y < 0>
- and y is less than 0
- </#if>
-</#if></programlisting>
-
- <note>
- <para>当你想测试是否 <literal>x > 0</literal> 或
- <literal>x >= 0</literal>,编写 <literal><#if x >
- 0></literal> 和 <literal><#if x >= 0></literal> 是错误的,
- 因为第一个 <literal>></literal> 会结束
- <literal>#if</literal> 标签。要这么来做,可以编写
- <literal><#if x gt 0></literal> 或 <literal><#if gte
- 0></literal>。也请注意,如果比较发生在括号内部,那么就没有这样的问题,
- 比如 <literal><#if
- foo.bar(x > 0)></literal> 就会得到想要的结果。</para>
- </note>
- </section>
- </section>
-
- <section xml:id="ref_directive_import">
- <title>import</title>
-
- <anchor xml:id="ref.directive.import"/>
-
- <indexterm>
- <primary>import directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#import <replaceable>path</replaceable> as <replaceable>hash</replaceable>></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>path</replaceable></literal>:模板的路径。
- 这是一个算作是字符串的表达式。(换句话说,它不是一个固定的字符串,
- 它可以是这样的一些东西,比如,<literal>profile.baseDir + "/menu.ftl"</literal>。)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>hash</replaceable></literal>:
- 访问命名空间的哈希表变量不带引号的名字。不是表达式。
- (如果要引入动态创建的名字,那么就不得不使用 <link
- linkend="faq_assign_to_dynamic_variable_name">这个技巧</link>。)</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>引入一个库。也就是说,它创建一个新的空命名空间,
- 然后在那个命名空间中执行给定
- <literal><replaceable>path</replaceable></literal> 参数中的模板,
- 所以模板用变量(宏,函数等)填充命名空间。
- 然后使得新创建的命名空间对哈希表的调用者可用。
- 这个哈希表变量将会在命名空间中,由 <literal>import</literal>
- (就像你可以用 <literal>assign</literal> 指令来创建一样。)
- 的调用者被创建成一个普通变量,名字就是
- <literal><replaceable>hash</replaceable></literal> 参数给定的。</para>
-
- <para>如果你用同一个 <literal><replaceable>path</replaceable></literal>
- 多次调用 <literal>import</literal>,它会创建命名空间,
- 但是只运行第一次 <literal>import</literal> 的调用。
- 后面的调用仅仅创建一个哈希表变量,你只是通过它来访问
- <emphasis>同一个</emphasis> 命名空间。</para>
-
- <para>由引入的模板打印的输出内容将会被忽略
- (不会在包含它的地方被插入)。模板的执行是来用变量填充命名空间,
- 而不是写到输出中。</para>
-
- <para>比如:</para>
-
- <programlisting role="template"><#import "/libs/mylib.ftl" as my>
-
-<@my.copyright date="1999-2002"/></programlisting>
-
- <para><literal><replaceable>path</replaceable></literal>
- 参数可以是一个相对路径,比如 <literal>"foo.ftl"</literal>
- 和 <literal>"../foo.ftl"</literal>,或者是像
- <literal>"/foo.ftl"</literal> 一样的绝对路径。
- 相对路径是相对于使用 <literal>import</literal> 指令模板的目录。
- 绝对路径是程序员配置 FreeMarker 时定义的相对于根路径
- (通常指代"模板的根目录")的路径。</para>
-
- <para>通常使用<literal>/</literal>(斜杠)来分隔路径组成,
- 而不是<literal>\</literal>(反斜杠)。如果你从你本地的文件系统中加载模板,
- 那么它使用反斜杠(比如在Windows环境下),FreeMarker 将会自动转换它们。</para>
-
- <para>像 <literal>include</literal> 指令一样,<link
- linkend="ref_directive_include_acquisition">获得机制</link> 和
- <link linkend="ref_directive_include_localized">本地化查找</link>
- 也可以用来解决路径问题。</para>
-
- <para><phrase role="forProgrammers">请注意,对于所有模板来说,
- 它可以自动做通用的引入操作,使用 <literal>Configuration</literal>
- 的"自动引入"设置就行了。</phrase></para>
-
- <para>如果你命名空间不是很了解,应该阅读: <xref
- linkend="dgui_misc_namespace"/></para>
- </section>
- </section>
-
- <section xml:id="ref_directive_include">
- <title>include</title>
-
- <anchor xml:id="ref.directive.include"/>
-
- <indexterm>
- <primary>include directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#include <replaceable>path</replaceable>></literal>
-或
-<literal><#include <replaceable>path</replaceable> <replaceable>options</replaceable>></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>path</replaceable></literal>:
- 要包含文件的路径;一个算作是字符串的表达式。(用其他话说,
- 它不用是一个固定的字符串,它也可以是像
- <literal>profile.baseDir + "/menu.ftl"</literal>这样的东西。)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>options</replaceable></literal>:
- 一个或多个这样的选项:
- <literal>encoding=<replaceable>encoding</replaceable></literal>,
- <literal>parse=<replaceable>parse</replaceable></literal></para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>encoding</replaceable></literal>:
- 算作是字符串的表达式</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>parse</replaceable></literal>:
- 算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>ignore_missing</replaceable></literal>:
- 算作是布尔值的表达式</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>你可以使用它在你的模板中插入另外一个 FreeMarker 模板文件
- (由 <literal><replaceable>path</replaceable></literal> 参数指定)。
- 被包含模板的输出格式是在 <literal>include</literal> 标签出现的位置插入的。
- 被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。
- <literal>include</literal> 指令不能由被包含文件的内容所替代,
- 它只是当 FreeMarker 每次在模板处理期间到达 <literal>include</literal>
- 指令时处理被包含的文件。所以对于如果 <literal>include</literal>
- 在 <literal>list</literal> 循环之中的例子,
- 你可以为每个循环周期内指定不同的文件名。</para>
-
- <note>
- <para>这个指令不能和JSP(Servlet)的include搞混,
- 因为它不涉及到Servlet容器中,只是处理应外一个FreeMarker模板,
- 不能"离开"FreeMarker。关于如何处理"JSP include",<link
- linkend="faq_servlet_include">请阅读...</link>。</para>
- </note>
-
- <para><literal><replaceable>path</replaceable></literal>
- 参数可以是如 <literal>"foo.ftl"</literal> 和
- <literal>"../foo.ftl"</literal> 一样的相对路径,或者是如
- <literal>"/foo.ftl"</literal> 这样的绝对路径。
- 相对路径是相对于使用 <literal>import</literal> 指令的模板文件夹。
- 绝对路径是相对于程序员在配置 FreeMarker 时定义的基路径
- (通常指代"模板的根路径")。</para>
-
- <note>
- <para>这和 FreeMarker 2.1 版本之前的处理方式不同,
- 之前的路径通常是绝对路径。为了保留原来的行为,
- 要在 <literal>Configuration</literal> 对象中开启经典的兼容模式。</para>
- </note>
-
- <para>通常使用 <literal>/</literal>(斜杠)来分隔路径成分,
- 而不是 <literal>\</literal>(反斜杠)。如果你从你本地的文件系统加载模板,
- 而它使用反斜杠(像Windows操作系统),也要使用 <literal>/</literal>。</para>
-
- <para>比如:</para>
-
- <para>假设 /common/copyright.ftl 包含:</para>
-
- <programlisting role="template">Copyright 2001-2002 ${me}<br>
-All rights reserved.</programlisting>
-
- <para>那么:</para>
-
- <programlisting role="template"><#assign me = "Juila Smith">
-<h1>Some test</h1>
-<p>Yeah.
-<hr>
-<emphasis><#include "/common/copyright.ftl"></emphasis></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"><h1>Some test</h1>
-<p>Yeah.
-<hr>
-<emphasis>Copyright 2001-2002 Juila Smith
-All rights reserved.</emphasis></programlisting>
-
- <para>支持的
- <literal><replaceable>options</replaceable></literal> 选项有:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>parse</literal>:如果它为 <literal>true</literal>,
- 那么被包含的文件将会当作FTL来解析,否则整个文件将被视为简单文本
- (也就是说不会在其中查找 FreeMarker 的结构)。如果你忽略了这个选项,
- 那么它默认是 <literal>true</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>encoding</literal>:被包含文件从包含它的文件继承的编码方式
- (实际就是字符集),除非你用这个选项来指定编码方式。
- 合法的名字有:ISO-8859-2,UTF-8,Shift_JIS,Big5,EUC-KR,GB2312。
- <phrase role="forProgrammers">编码名称要和java.io.InputStreamReader
- 中支持的那些一致(对于Java API 1.3版本:MIME
- 希望的字符集是从IANA字符集注册处得到的)</phrase></para>
- </listitem>
-
- <listitem>
- <para><literal>ignore_missing</literal>:当为
- <literal>true</literal>,模板引用为空时压制错误,而
- <literal><#include ...></literal> 不会输出任何东西。当为
- <literal>false</literal> 时,如果模板不存在,
- 那么模板处理就会发生错误并停止。如果忽略这个选项,那么它的默认值是
- <literal>false</literal>。</para>
- </listitem>
- </itemizedlist>
-
- <para>比如:</para>
-
- <programlisting role="template"><#include "/common/navbar.html" parse=false encoding="Shift_JIS"></programlisting>
-
- <para><phrase role="forProgrammers">请注意,
- 对于所有模板可能会用 <literal>Configuration</literal>
- 的"自动包含"设置自动处理通用的包含物。</phrase></para>
-
- <section xml:id="ref_directive_include_acquisition">
- <title>使用获得机制</title>
-
- <indexterm>
- <primary>acquisition</primary>
- </indexterm>
-
- <para>有一个特殊的路径组成,是用一个星号(<literal>*</literal>)来代表的。
- 它被解释为"当前目录或其他任意它的父目录"。因此,
- 如果模板在 <literal>/foo/bar/template.ftl</literal> 位置上,有下面这行:</para>
-
- <programlisting role="template"><#include "*/footer.ftl"></programlisting>
-
- <para>那么引擎就会在下面的位置上寻找模板,并按这个顺序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>/foo/bar/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/footer.ftl</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>该机制被称为 <emphasis role="term">acquisition</emphasis>
- 并允许设计者在父目录中放置通用的被包含的文件,
- 而且当需要时在每个子路径基础上重新定义它们。
- 我们说包含它们的模板获得了从包含它的第一个父目录中的模板。请注意,
- 你不但可以在星号的右面指定一个模板的名字,也可以指定一个子路径。
- 也就是说,如果前面的模板由下面这个所替代:</para>
-
- <programlisting role="template"><#include "*/commons/footer.ftl"></programlisting>
-
- <para>那么引擎将会从下面的路径开始寻找模板,并按这个顺序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>/foo/bar/commons/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/commons/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/commons/footer.ftl</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>最终,星号不再是路径的第一个元素:</para>
-
- <programlisting role="template"><#include "commons/*/footer.ftl"></programlisting>
-
- <para>会让引擎将会从下面的路径开始寻找模板,并按这个顺序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>/foo/bar/commons/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/bar/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/footer.ftl</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>然而,在路径中最多只能有一个星号。
- 指定多余一个星号会导致模板不能被发现。</para>
- </section>
-
- <section xml:id="ref_directive_include_localized">
- <title>本地化查找</title>
-
- <indexterm>
- <primary>localization</primary>
- </indexterm>
-
- <para>本地化是语言和可选的国家或方言标识符
- (加上可能的更多变体标识符,比如 <quote>MAC</quote>)。
- 无论何时模板被请求,期望的本地化都会被指定(明确或含蓄),
- FreeMarker 会试图找到变化的模板来匹配本地化环境。当模板包含或引入其它模板时,
- 在内部也会被请求一个本地化环境,也就是 <literal>locale</literal> 配置的本地化,
- 通常它是顶级模板的本地化设置。</para>
-
- <para>假设模板使用本地化 <literal>en_US</literal> 来加载,
- 就是美国英语。当包含其它模板时:</para>
-
- <programlisting role="template"><#include "footer.ftl"></programlisting>
-
- <para>引擎实际上就会寻找一些模板,并按照这个顺序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>footer_en_US.ftl</literal>,</para>
- </listitem>
-
- <listitem>
- <para><literal>footer_en.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>footer.ftl</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>它会使用第一个存在的。</para>
-
- <para>请注意,如果 FreeMarker 查找的本地化变化是由程序员配置的,
- 那么这里我们只能描述默认的行为。<phrase
- role="forProgrammers"> 可以使用
- <literal>localized_lookup</literal> 设置来禁用本地化查找
- (<literal>Configuration.setLocalizedLookup(boolean)</literal>)。
- 而且,可以使用 <literal>template_lookup_strategy</literal>
- 设置来自行定义推导出的模板名称序列
- (<literal>Configuration.setTemplateLookupStrategy(TemplateLookupStrategy)</literal>)。
- </phrase></para>
-
- <para>当你同时使用获得机制(也就是路径中的 <literal>*</literal> 步骤)
- 和本地化查找时,在父目录中有指定本地化的模板优先于在子目录中有很少本地化的模板。
- 假设你使用下面的代码来包含 <literal>/foo/bar/template.ftl</literal>:</para>
-
- <programlisting role="template"><#include "*/footer.ftl"></programlisting>
-
- <para>引擎将会查找这些模板,并按照这个顺序:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>/foo/bar/footer_en_US.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/footer_en_US.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/footer_en_US.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/bar/footer_en.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/footer_en.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/footer_en.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/bar/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/foo/footer.ftl</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>/footer.ftl</literal></para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
- </section>
-
- <section xml:id="ref_directive_list">
- <title>list, else, items, sep, break</title>
-
- <anchor xml:id="ref.directive.list"/>
-
- <indexterm>
- <primary>list directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>iterate</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <para>形式 1:</para>
-
- <programlisting role="metaTemplate"><literal><#list <replaceable>sequence</replaceable> as <replaceable>item</replaceable>>
- <replaceable>Part repeated for each item</replaceable>
-<#else>
- <replaceable>Part executed when there are 0 items</replaceable>
-</#list></literal></programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>else</literal> 部分是可选的,
- 而且仅仅从 FreeMarker 2.3.23 版本开始支持。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>sequence</replaceable></literal>:
- 将我们想要迭代的项,算作是序列或集合的表达式</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>item</replaceable></literal>:
- <link linkend="dgui_misc_var">循环变量</link> 的名称 (不是表达式)</para>
- </listitem>
-
- <listitem>
- <para>在标签之间的多个 <quote>parts</quote> 可以是任意的FTL
- (包括嵌套的 <literal>list</literal>)</para>
- </listitem>
- </itemizedlist>
-
- <para>形式 2 (从 FreeMarker 2.3.23 版本开始):</para>
-
- <programlisting role="metaTemplate"><literal><#list <replaceable>sequence</replaceable>>
- <replaceable>Part executed once if we have more than 0 items</replaceable>
- <#items as <replaceable>item</replaceable>>
- <replaceable> Part repeated for each item</replaceable>
- </#items>
- <replaceable>Part executed once if we have more than 0 items</replaceable>
-<#else>
- <replaceable>Part executed when there are 0 items</replaceable>
-</#list></literal></programlisting>
-
- <para>这里:和上面形式1的 <quote>这里</quote> 部分相同。</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <section>
- <title>最简形式</title>
-
- <para>假设 <literal>users</literal> 包含
- <literal>['Joe', 'Kate', 'Fred']</literal> 序列:</para>
-
- <programlisting role="template"><#list users as user>
- <p>${user}
-</#list></programlisting>
-
- <programlisting role="output"> <p>Joe
- <p>Kate
- <p>Fred</programlisting>
-
- <para><literal>list</literal> 指令执行在
- <literal>list</literal> 开始标签和
- <literal>list</literal> 结束标签 (
- <literal>list</literal> 中间的部分) 之间的代码,
- 对于在序列(或集合)中每个值指定为它的第一个参数。
- 对于每次迭代,循环变量(本例中的 <literal>user</literal>)将会存储当前项的值。</para>
-
- <para>循环变量(<literal>user</literal>) 仅仅存在于
- <literal>list</literal> 标签体内。
- 而且从循环中调用的宏/函数不会看到它(就像它只是局部变量一样)。</para>
- </section>
-
- <section>
- <title>else 指令</title>
-
- <anchor xml:id="ref.directive.list.else"/>
-
- <indexterm>
- <primary>else directive inside list</primary>
- </indexterm>
-
- <note>
- <para>在 <literal>list</literal> 中的 <literal>else</literal>
- 仅从 FreeMarker 2.3.23 版本开始支持。</para>
- </note>
-
- <para>当没有迭代项时,才使用 <literal>else</literal> 指令,
- 可以输出一些特殊的内容而不只是空在那里:</para>
-
- <programlisting role="template"><#list users as user>
- <p>${user}
-<#else>
- <p>No users
-</#list></programlisting>
-
- <para>该输出和之前示例是相同的,除了当
- <literal>users</literal> 包含0项时:</para>
-
- <programlisting role="output"> <p>No users</programlisting>
-
- <para>请注意,循环变量 (<literal>user</literal>) 在
- <literal>else</literal> 标签和
- <literal>list</literal> 结束标签中间不存在,
- 因为那部分不是循环中的部分。</para>
-
- <para><literal>else</literal> 必须是真的在 (也就是在源代码中)
- <literal>list</literal> 指令体内部。也就是说,
- 不能将它移出到宏或包含的模板中。</para>
- </section>
-
- <section>
- <title>items 指令</title>
-
- <anchor xml:id="ref.directive.items"/>
-
- <indexterm>
- <primary>items directive</primary>
- </indexterm>
-
- <note>
- <para><literal>items</literal> 从 FreeMarker 2.3.23 版本开始存在</para>
- </note>
-
- <para>如果不得不在第一列表项之前或在最后一个列表项之后打印一些东西,
- 那么就要使用 <literal>items</literal> 指令,但至少要有一项。典型的示例为:</para>
-
- <programlisting role="template"><#list users>
- <ul>
- <#items as user>
- <li>${user}</li>
- </#items>
- </ul>
-</#list></programlisting>
-
- <programlisting role="output"> <ul>
- <li>Joe</li>
- <li>Kate</li>
- <li>Fred</li>
- </ul></programlisting>
-
- <para>如果没有迭代项,那么上面的代码不会输出任何内容,
- 因此不用以空的 <literal><ul></ul></literal> 来结束。</para>
-
- <para>也就是说,当 <literal>list</literal> 指令没有
- <literal>as <replaceable>item</replaceable></literal> 参数,
- 如果只有一个迭代项,指令体中的代码仅仅执行一次,否则就不执行。
- 必须内嵌的 <literal>items</literal> 指令体会对每个迭代项执行,
- 那么 <literal>items</literal> 指令使用 <literal>as
- <replaceable>item</replaceable></literal> 定义循环变量,而不是
- <literal>list</literal>。</para>
-
- <para>有 <literal>items</literal> 的 <literal>list</literal>
- 指令也可以有 <literal>else</literal> 指令:</para>
-
- <programlisting role="template"><#list users>
- <ul>
- <#items as user>
- <li>${user}</li>
- </#items>
- </ul>
-<#else>
- <p>No users
-</#list></programlisting>
-
- <para>更多细节:</para>
-
- <itemizedlist>
- <listitem>
- <para>解析器会检查没有 <literal>as <replaceable>item</replaceable></literal>
- 参数的 <literal>list</literal> 通常会有嵌入的 <literal>items</literal>
- 指令,该 <literal>items</literal> 指令通常会有一个包围的
- <literal>list</literal>,它没有 <literal>as <replaceable>item</replaceable></literal>
- 参数。当模板解析时就会检查,而不是当模板执行的时候。因此,这些规则也适用于FTL源代码本身,
- 所以不能将 <literal>items</literal> 移出到宏或者被包含的模板中。</para>
- </listitem>
-
- <listitem>
- <para><literal>list</literal> 可以有多个 <literal>items</literal> 指令,
- 但是只有一个允许执行(直到不离开或重新进入包围的 <literal>list</literal> 指令);
- 之后试图调用 <literal>items</literal> 会发生错误。所以多个
- <literal>items</literal> 可以用于不同的
- <literal>if</literal>-<literal>else</literal> 分支中去,但不能迭代两次。</para>
- </listitem>
-
- <listitem>
- <para><literal>items</literal> 指令不能有它自己的嵌入
- <literal>else</literal> 指令,只能被包含的
- <literal>list</literal> 可以有。</para>
- </listitem>
-
- <listitem>
- <para>循环变量 (<literal>user</literal>) 仅仅存在于
- <literal>items</literal> 指令体内部。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>sep 指令</title>
-
- <anchor xml:id="ref.directive.sep"/>
-
- <indexterm>
- <primary>sep directive</primary>
- </indexterm>
-
- <note>
- <para><literal>sep</literal> 从 FreeMarker 2.3.23 版本开始存在。</para>
- </note>
-
- <para>当不得不显示介于每个迭代项(但不能在第一项之前或最后一项之后)
- 之间的一些内容时,可以使用 <literal>sep</literal>。例如:</para>
-
- <programlisting role="template"><#list users as user>${user}<emphasis><#sep>, </emphasis></#list></programlisting>
-
- <programlisting role="output">Joe, Kate, Fred</programlisting>
-
- <para>上面的 <literal><#sep>, </#list></literal> 是
- <literal><#sep>, </#sep></#list></literal> 的简写;
- 如果将它放到被包含的指令关闭的位置时,<literal>sep</literal>
- 结束标签可以忽略。下面的示例中,就不能使用该简写
- (HTML标签不会结束任何代码,它们只是 FreeMarker 输出的原生文本):</para>
-
- <programlisting role="template"><#list users as user>
- <div>
- ${user}<emphasis><#sep>, </#sep></emphasis>
- </div>
-</#list></programlisting>
-
- <para><literal>sep</literal> 是编写 <literal><#if
- <replaceable>item</replaceable>?has_next>...</#if></literal>
- 的方便形式,有 <literal>list</literal> 或 <literal>items</literal>
- 循环变量时,它就可以使用,并且不限次数。而且,
- 也可以有任意的 FTL 作嵌入的内容。</para>
-
- <para>解析器会检查在 <literal>list <replaceable>...</replaceable> as
- item</literal> 内部使用的 <literal>sep</literal> 或者
- <literal>items</literal> 指令,所以不能将 <literal>sep</literal>
- 从重复的部分移出到宏或被包含的模板中。</para>
- </section>
-
- <section>
- <title>break 指令</title>
-
- <anchor xml:id="ref.directive.list.break"/>
-
- <indexterm>
- <primary>break directive</primary>
- </indexterm>
-
- <para>可以使用 <literal>break</literal> 指令在迭代的任意点退出。例如:</para>
-
- <programlisting role="template"><#list 1..10 as x>
- ${x}
- <#if x == 3>
- <emphasis><#break></emphasis>
- </#if>
-</#list></programlisting>
-
- <programlisting role="output"> 1
- 2
- 3</programlisting>
-
- <para><literal>break</literal> 指令可以放在
- <literal>list</literal> 中的任意位置,直到有
- <literal>as <replaceable>item</replaceable></literal> 参数,
- 否则,可以放在 <literal>items</literal> 指令中的任意位置。
- 如果 <literal>break</literal> 在 <literal>items</literal> 内部,
- 那么就只能从 <literal>items</literal> 开始时存在,而不能从
- <literal>list</literal> 开始时存在。通常来说,<literal>break</literal>
- 将仅存在于为每个迭代项调用的指令体中,而且只能存在于这样的指令中。
- 例如不能在 <literal>list</literal> 的 <literal>else</literal> 部分使用
- <literal>break</literal>,除非 <literal>list</literal> 内嵌到了其它
- 可以 <literal>break</literal> 的指令中。</para>
-
- <para>像 <literal>else</literal> 和
- <literal>items</literal>, <literal>break</literal>
- 只能在指令体内部使用,而不能移出到宏或被包含的模板中。</para>
- </section>
-
- <section>
- <title>访问迭代状态</title>
-
- <indexterm>
- <primary>iteration state</primary>
- </indexterm>
-
- <indexterm>
- <primary>listing state</primary>
- </indexterm>
-
- <para>从 2.3.23 版本开始, <link
- linkend="ref_builtins_loop_var">循环变量内建函数</link>
- 就是访问当前迭代状态的最佳方式。例如,这里我们使用
- <literal>counter</literal> 和 <literal>item_parity</literal>
- 循环变量内建函数(在 <link
- linkend="ref_builtins_loop_var">循环变量内建函数参考</link>
- 中查看它们全部):</para>
-
- <programlisting role="template"><#list users>
- <table>
- <#items as user>
- <tr class="${user<emphasis>?item_parity</emphasis>}Row">
- <td>${user<emphasis>?counter</emphasis>}
- <td>${user}
- </#items>
- </table>
-</#list></programlisting>
-
- <programlisting role="output"> <table>
- <tr class="<emphasis>odd</emphasis>Row">
- <td><emphasis>1</emphasis>
- <td>Joe
- <tr class="<emphasis>even</emphasis>Row">
- <td><emphasis>2</emphasis>
- <td>Kate
- <tr class="<emphasis>odd</emphasis>Row">
- <td><emphasis>3</emphasis>
- <td>Fred
- </table></programlisting>
-
- <para>在 2.3.22 和之前的版本中,有两个额外的循环变量来获得迭代状态
- (出于向后兼容考虑,它们仍然存在):</para>
-
- <itemizedlist>
- <listitem>
- <para><literal><replaceable>item</replaceable>_index</literal>
- (<emphasis>已废弃</emphasis>,由
- <literal><replaceable>item</replaceable>?index</literal> 代替):
- 循环中当前项的索引(从0开始的数字)。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>item</replaceable>_has_next</literal>
- (<emphasis>已废弃</emphasis>,由
- <literal><replaceable>item</replaceable>?has_next</literal> 代替):
- 辨别当前项是否是序列的最后一项的布尔值。</para>
- </listitem>
- </itemizedlist>
-
- <para>所以在上面的示例中,可以将
- <literal>${user?counter}</literal> 替换为 <literal>${user_index +
- 1}</literal>。</para>
- </section>
-
- <section>
- <title>相互嵌套循环</title>
-
- <para>很自然地,<literal>list</literal> 或
- <literal>items</literal> 可以包含更多
- <literal>list</literal>:</para>
-
- <programlisting role="template"><#list 1..2 as i>
- <#list 1..3 as j>
- i = ${i}, j = ${j}
- </#list>
-</#list></programlisting>
-
- <programlisting role="output"> i = 1, j = 1
- i = 1, j = 2
- i = 1, j = 3
- i = 2, j = 1
- i = 2, j = 2
- i = 2, j = 3</programlisting>
-
- <para>允许使用冲突的循环变量名称,比如:</para>
-
- <programlisting role="template"><#list 1..2 as i>
- Outer: ${i}
- <#list 10..12 as i>
- Inner: ${i}
- </#list>
- Outer again: ${i}
-</#list></programlisting>
-
- <programlisting role="output"> Outer: 1
- Inner: 10
- Inner: 11
- Inner: 12
- Outer again: 1
- Outer: 2
- Inner: 10
- Inner: 11
- Inner: 12
- Outer again: 2</programlisting>
- </section>
-
- <section>
- <title>Java程序员请注意</title>
-
- <para><phrase role="forProgrammers">如果经典兼容模式下
- <literal>list</literal> 接受标量,并将它视为单元素序列。</phrase></para>
-
- <para><phrase role="forProgrammers">如果传递包装了
- <literal>java.util.Iterator</literal> 的集合到
- <literal>list</literal> 中,那么只能迭代其中的元素一次,因为
- <literal>Iterator</literal> 是它们天然的一次性对象。
- 当视图再次去迭代这样的集合变量时,会发生错误并中止模板处理。</phrase></para>
- </section>
- </section>
- </section>
-
- <section xml:id="ref_directive_local">
- <title>local</title>
-
- <anchor xml:id="ref.directive.local"/>
-
- <indexterm>
- <primary>local directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#local <replaceable>name</replaceable>=<replaceable>value</replaceable>></literal>
-或
-<literal><#local <replaceable>name1</replaceable>=<replaceable>value1</replaceable> <replaceable>name2</replaceable>=<replaceable>value2</replaceable> <replaceable>... nameN</replaceable>=<replaceable>valueN</replaceable>></literal>
-或
-<literal><#local <replaceable>name</replaceable>>
- <replaceable>capture this</replaceable>
-</#local>
-</literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:
- 在root中局部对象的名称。它不是一个表达式。但它可以被写作是字符串形式,
- 如果变量名包含保留字符,这是很有用的,比如
- <literal><#local "foo-bar" = 1></literal>。
- 请注意,这个字符串没有扩展插值(如<literal>"${foo}"</literal>)。</para>
- </listitem>
-
- <listitem>
- <para><literal>=</literal>:赋值操作符,也可以简写的赋值操作符之一
- (<literal>++</literal>,<literal>+=</literal> 等...),和
- <link linkend="ref_directive_assign">the
- <literal>assign</literal> 指令</link> 相似。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value</replaceable></literal>:
- 存储的值,是表达式。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>它和 <link linkend="ref.directive.assign">assign
- 指令</link> 类似,但是它创建或替换局部变量。
- 这仅仅在宏和方法的内部定义才会有作用。</para>
-
- <para>要获得更多关于变量的信息,可以阅读:<xref
- linkend="dgui_misc_var"/></para>
- </section>
- </section>
-
- <section xml:id="ref_directive_macro">
- <title>macro, nested, return</title>
-
- <anchor xml:id="ref.directive.macro"/>
-
- <indexterm>
- <primary>macro directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>nested directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>return directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#macro <replaceable>name</replaceable> <replaceable>param1</replaceable> <replaceable>param2</replaceable> <replaceable>... paramN</replaceable>>
- <replaceable>...</replaceable>
- <#nested <replaceable>loopvar1</replaceable>, <replaceable>loopvar2</replaceable>, <replaceable>...</replaceable>, <replaceable>loopvarN</replaceable>>
- <replaceable>...</replaceable>
- <#return>
- <replaceable>...</replaceable>
-</#macro></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:
- 宏变量的名称,它不是表达式。和 <link
- linkend="dgui_template_exp_var_toplevel">顶层变量</link>
- 的语法相同,比如 <literal>myMacro</literal> 或 <literal>my\-macro</literal>。
- 然而,它可以被写成字符串的形式,如果宏名称中包含保留字符时,这是很有用的,
- 比如 <literal><#macro "foo~bar"><replaceable>...</replaceable></literal>。
- 注意这个字符串没有扩展插值(如 <literal>"${foo}"</literal>)。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>param1</replaceable></literal>,
- <literal><replaceable>param2</replaceable></literal>,等...:
- <link linkend="dgui_misc_var">局部变量</link> 的名称,存储参数的值
- (不是表达式),在 <literal>=</literal> 号后面和默认值(是表达式)是可选的。
- 默认值也可以是另外一个参数,比如 <literal><#macro section title
- label=title></literal>。参数名称和 <link
- linkend="dgui_template_exp_var_toplevel">顶层变量</link>
- 的语法相同,所以有相同的特性和限制。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>catch-all parameter</primary>
- </indexterm><indexterm>
- <primary>variable number of parameters</primary>
- </indexterm><literal><replaceable>paramN</replaceable></literal>,
- 最后一个参数,可能会有三个点(<literal>...</literal>),
- 这就意味着宏接受可变数量的参数,不匹配其它参数的参数可以作为最后一个参数
- (也被称作笼统参数)。当宏被命名参数调用,
- <literal><replaceable>paramN</replaceable></literal>
- 将会是包含宏的所有未声明的键/值对的哈希表。当宏被位置参数调用,
- <literal><replaceable>paramN</replaceable></literal> 将是额外参数的序列。
- (在宏内部,要查找参数,可以使用
- <literal><replaceable>myCatchAllParam</replaceable>?is_sequence</literal>。)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>loopvar1</replaceable></literal>,
- <literal><replaceable>loopvar2</replaceable></literal>等...:
- 可选的,<link linkend="dgui_misc_var">循环变量</link> 的值,
- 是 <literal>nested</literal> 指令想为嵌套内容创建的。这些都是表达式。</para>
- </listitem>
- </itemizedlist>
-
- <para><literal>return</literal> 和 <literal>nested</literal>
- 指令是可选的,而且可以在 <literal><#macro
- <replaceable>...</replaceable>></literal> 和
- <literal></#macro></literal> 之间被用在任意位置和任意次数。</para>
-
- <para>没有默认值的参数必须在有默认值参数
- (<literal><replaceable>paramName</replaceable>=<replaceable>defaultValue</replaceable></literal>)
- 之前。</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>创建一个宏变量(在当前命名空间中,如果你知道命名空间的特性)。
- 如果你对宏和自定义指令不了解,你应该阅读 <link
- linkend="dgui_misc_userdefdir">自定义指令指南</link>。</para>
-
- <para>宏变量存储模板片段(称为宏定义体)可以被用作 <link
- linkend="ref.directive.userDefined">自定义指令</link>。
- 这个变量也存储自定义指令的被允许的参数名。当你将这个变量作为指令时,
- 你必须给所有参数赋值,除了有默认值的参数。
- 默认值当且仅当你调用宏而不给参数赋值时起作用。</para>
-
- <para>变量会在模板开始时被创建;而不管 <literal>macro</literal>
- 指令放置在模板的什么位置。因此,这样也可以:</para>
-
- <programlisting role="template"><#-- call the macro; the macro variable is already created: -->
-<@test/>
-...
-
-<#-- create the macro variable: -->
-<#macro test>
- Test text
-</#macro></programlisting>
-
- <para>然而,如果宏定义被插在 <literal>include</literal> 指令中,
- 它们直到 FreeMarker 执行 <literal>include</literal> 指令时才会可用。</para>
-
- <para>例如:没有参数的宏:</para>
-
- <programlisting role="template"><#macro test>
- Test text
-</#macro>
-<#-- call the macro: -->
-<@test/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Test text
- </programlisting>
-
- <para>示例:有参数的宏:</para>
-
- <programlisting role="template"><#macro test foo bar baaz>
- Test text, and the params: ${foo}, ${bar}, ${baaz}
-</#macro>
-<#-- call the macro: -->
-<@test foo="a" bar="b" baaz=5*5-2/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Test text, and the params: a, b, 23
- </programlisting>
-
- <para>示例:有参数和默认值参数的宏:</para>
-
- <programlisting role="template"><#macro test foo bar="Bar" baaz=-1>
- Test text, and the params: ${foo}, ${bar}, ${baaz}
-</#macro>
-<@test foo="a" bar="b" baaz=5*5-2/>
-<@test foo="a" bar="b"/>
-<@test foo="a" baaz=5*5-2/>
-<@test foo="a"/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Test text, and the params: a, b, 23
- Test text, and the params: a, b, -1
- Test text, and the params: a, Bar, 23
- Test text, and the params: a, Bar, -1
- </programlisting>
-
- <para>示例:更为复杂的宏。</para>
-
- <programlisting role="template"><#macro list title items>
- <p>${title?cap_first}:
- <ul>
- <#list items as x>
- <li>${x?cap_first}
- </#list>
- </ul>
-</#macro>
-<@list items=["mouse", "elephant", "python"] title="Animals"/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>Animals:
- <ul>
- <li>Mouse
- <li>Elephant
- <li>Python
- </ul>
- </programlisting>
-
- <para>示例:支持多个参数和命名参数的宏:</para>
-
- <programlisting role="template"><#macro img src extra...>
- <img src="/context${src?html}"
- <#list extra?keys as attr>
- ${attr}="${extra[attr]?html}"
- </#list>
- >
-</#macro>
-<@img src="/images/test.png" width=100 height=50 alt="Test"/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <img src="/context/images/test.png"
- alt="Test"
- height="50"
- width="100"
- ></programlisting>
-
- <para>示例:支持多个位置参数的宏,不管是否使用命名或位置参数传递:</para>
-
- <programlisting role="template"><#macro m a b ext...>
- a = ${a}
- b = ${b}
- <#if ext?is_sequence>
- <#list ext as e>
- ${e?index} = ${e}
- </#list>
- <#else>
- <#list ext?keys as k>
- ${k} = ${ext[k]}
- </#list>
- </#if>
-</#macro>
-
-<@m 1 2 3 4 5 />
-
-<@m a=1 b=2 c=3 d=4 e=5 data\-foo=6 myns\:bar=7 /></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> a = 1
- b = 2
- 0 = 3
- 1 = 4
- 2 = 5
-
- a = 1
- b = 2
- c = 3
- d = 4
- e = 5
- data-foo=6
- myns:bar=7</programlisting>
-
- <warning>
- <para>当前,命名的笼统参数是无序的,也就是说,不知道它们枚举时的顺序。
- 那么它们不会按相同传递顺序返回(上述示例输出相同的顺序只是为了理解)。</para>
- </warning>
-
- <section>
- <title>nested</title>
-
- <anchor xml:id="ref.directive.nested"/>
-
- <para><literal>nested</literal> 指令执行自定义指令开始和结束标签中间的模板片段。
- 嵌套的片段可以包含模板中任意合法的内容:插值,指令等...它在上下文环境中被执行,
- 也就是宏被调用的地方,而不是宏定义体的上下文中。因此,比如,
- 你不能看到嵌套部分的宏的局部变量。如果你没有调用 <literal>nested</literal> 指令,
- 自定义指令开始和结束标记中的部分将会被忽略。</para>
-
- <para>比如:</para>
-
- <programlisting role="template"><#macro do_twice>
- 1. <#nested>
- 2. <#nested>
-</#macro>
-<@do_twice>something</@do_twice></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> 1. something
- 2. something
- </programlisting>
-
- <para>nested 指令可以对嵌套内容创建循环变量。例如:</para>
-
- <programlisting role="template"><#macro do_thrice>
- <#nested <emphasis>1</emphasis>>
- <#nested <emphasis>2</emphasis>>
- <#nested <emphasis>3</emphasis>>
-</#macro>
-<@do_thrice <emphasis>; x</emphasis>>
- ${<emphasis>x</emphasis>} Anything.
-</@do_thrice></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> 1 Anything.
- 2 Anything.
- 3 Anything.
- </programlisting>
-
- <para>更为复杂的示例:</para>
-
- <programlisting role="template"><#macro repeat count>
- <#list 1..count as x>
- <#nested <emphasis>x, x/2, x==count</emphasis>>
- </#list>
-</#macro>
-<@repeat count=4 ; <emphasis>c, halfc, last</emphasis>>
- ${<emphasis>c</emphasis>}. ${<emphasis>halfc</emphasis>}<#if <emphasis>last</emphasis>> Last!</#if>
-</@repeat></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> 1. 0.5
- 2. 1
- 3. 1.5
- 4. 2 Last!
- </programlisting>
- </section>
-
- <section>
- <title>return</title>
-
- <anchor xml:id="ref.directive.macro.return"/>
-
- <para>使用 <literal>return</literal> 指令,
- 你可以在任意位置留下一个宏或函数定义。比如:</para>
-
- <programlisting role="template"><#macro test>
- Test text
- <#return>
- Will not be printed.
-</#macro>
-<@test/></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Test text
- </programlisting>
- </section>
- </section>
- </section>
-
- <section xml:id="ref_directive_noparse">
- <title>noparse</title>
-
- <anchor xml:id="ref.directive.noparse"/>
-
- <indexterm>
- <primary>noparse directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#noparse>
- <replaceable>...</replaceable>
-</#noparse></literal>
-</programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>FreeMarker 不会在这个指令体中间寻找FTL标签,
- 插值和其他特殊的字符序列,除了noparse的结束标记。</para>
-
- <para>比如:</para>
-
- <programlisting role="template">Example:
---------
-
-<emphasis><#noparse></emphasis>
- <#list animals as animal>
- <tr><td>${animal.name}<td>${animal.price} Euros
- </#list>
-<emphasis></#noparse></emphasis></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Example:
---------
-
- <#list animals as animal>
- <tr><td>${animal.name}<td>${animal.price} Euros
- </#list>
- </programlisting>
- </section>
- </section>
-
- <section xml:id="ref_directive_nt">
- <title>nt</title>
-
- <anchor xml:id="ref.directive.nt"/>
-
- <indexterm>
- <primary>nt directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>trimmer directives</primary>
- </indexterm>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>trimming</secondary>
- </indexterm>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>stripping</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#nt></literal>
-</programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>"不要削减"。该指令禁用行中出现的 <link
- linkend="dgui_misc_whitespace_stripping">剥离空白</link>。
- 它也关闭其他同一行中出现的削减指令(<literal>t</literal>,
- <literal>rt</literal>,<literal>lt</literal>的效果)。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_setting">
- <title>setting</title>
-
- <anchor xml:id="ref.directive.setting"/>
-
- <indexterm>
- <primary>setting directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#setting <replaceable>name</replaceable>=<replaceable>value</replaceable>></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:
- 设置的名称。不是表达式!</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value</replaceable></literal>:
- 设置的新值,是表达式。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>为进一步的处理而设置。设置是影响 FreeMarker 行为的值。
- 新值仅仅在被设置的模板处理时出现,而且不触碰模板本身。
- 设置的初始值是由程序员设定的 <phrase
- role="forProgrammers">(参考: <xref
- linkend="pgui_config_settings"/>)</phrase>。</para>
-
- <para>支持的设置有:</para>
-
- <itemizedlist>
- <listitem>
- <para><indexterm>
- <primary>locale</primary>
- </indexterm><literal>locale</literal>:输出的本地化(语言)。
- 它可以影响数字,日期等显示格式。它的值是由语言编码
- (小写两个字母的ISO-639编码)和可选的国家码
- (大写的两个字母ISO-3166编码)组成的字符串,它们以下划线相分隔,
- 如果我们已经指定了国家那么一个可选的不同编码
- (不是标准的)会以下划线分隔开国家。合法的值示例:<literal>en</literal>,
- <literal>en_US</literal>,<literal>en_US_MAC</literal>。
- FreeMarker 会尝试使用特定可用的本地化设置,所以如果你指定了
- <literal>en_US_MAC</literal>,但是它不被知道,那么它会尝试
- <literal>en_US</literal>,然后尝试 <literal>en</literal>,
- 然后是计算机(可能是由程序员设置的)默认的本地化设置。</para>
- </listitem>
-
- <listitem>
- <para xml:id="ref.setting.number_format"><indexterm>
- <primary>number_format</primary>
- </indexterm><indexterm>
- <primary>format</primary>
-
- <secondary>number</secondary>
- </indexterm><literal>number_format</literal>:
- 当没有指定确定的格式化形式时,用来转化数字到字符串形式的数字格式化设置。
- 可以是下列中的一个预定义值 <literal>number</literal>(默认的),
- <literal>computer</literal>,<literal>currency</literal>,
- 或 <literal>percent</literal>。此外,以 <link
- xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/text/DecimalFormat.html">Java小数数字格式化语法</link> 书写的任意的格式化形式也可以被指定。
- 更多格式形式内容: <link
- linkend="ref_builtin_string_for_number"><literal>string</literal>
- 内建函数</link>。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>boolean_format</primary>
- </indexterm><indexterm>
- <primary>format</primary>
-
- <secondary>boolean</secondary>
- </indexterm><literal>boolean_format</literal>:
- 以逗号分隔的一对字符串来分别展示 true 和 false 值,
- 当没有指定确定的格式时(比如在
- <literal>${<replaceable>booleanValue</replaceable>}</literal> 中),
- 将转换布尔值到字符串。请注意,当前的空格没有从该字符串中移除,
- 所以不要将空格放在逗号后面。默认值是 <literal>"true,false"</literal>。
- 但是 FreeMarker 会拒绝为 <literal>${<replaceable>booleanValue</replaceable>}</literal>
- 使用特定值,而需要使用 <literal>${<replaceable>booleanValue</replaceable>?c}</literal>
- 来代替(从 2.3.21 版本开始有效)。对于其它任意值,比如 <literal>"Y,N"</literal>,
- <literal>${<replaceable>booleanValue</replaceable>}</literal> 也是有效的。
- 请参考:<link
- linkend="ref_builtin_string_for_boolean"><literal>string</literal>
- 内建函数</link>。</para>
- </listitem>
-
- <listitem xml:id="topic.dateTimeFormatSettings">
- <para xml:id="topic_date_format_settings"><indexterm>
- <primary>date_format</primary>
- </indexterm><indexterm>
- <primary>time_format</primary>
- </indexterm><indexterm>
- <primary>datetime_format</primary>
- </indexterm><indexterm>
- <primary>format</primary>
-
- <secondary>date</secondary>
- </indexterm><indexterm>
- <primary>XML Schema date rendering</primary>
- </indexterm><indexterm>
- <primary>XML Schema time rendering</primary>
- </indexterm><indexterm>
- <primary>XML Schema dateTime rendering</primary>
- </indexterm><indexterm>
- <primary>ISO 8601</primary>
- </indexterm> <literal>date_format</literal>,
- <literal>time_format</literal>,
- <literal>datetime_format</literal>:当没有通过 <link
- linkend="ref_builtin_string_for_date"><literal>string</literal>
- 内建函数</link>(或相反)指定确定的格式时,格式将日期/时间/日期-时间值
- (Java <literal>java.util.Date</literal> 和它的子类)转换为字符串,
- 比如 <literal>${someDate}</literal>。<literal>date_format</literal>
- 设置仅仅格式于存储的无时间部分的值,<literal>time_format</literal>
- 仅仅格式于存储无日期部分的值,而 <literal>datetime_format</literal>
- 仅仅格式于日期-时间值。除了当它应用于字符串值时,这些设置也影响进行了 <link
- linkend="ref_builtin_string_date"><literal>?time</literal>,
- <literal>?date</literal>, 和
- <literal>?datetime</literal></link> 操作的格式。</para>
-
- <para><remark>下面的部分是从 Configurable.setDateTimeFormat(String) 的JavaDoc
- 中复制粘贴的;保持它们同步。</remark>
- 可能的设置是(引号标记不是值本身的一部分):</para>
-
- <itemizedlist>
- <listitem>
- <para><link
- xlink:href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">
- Java 的 <literal>SimpleDateFormat</literal> 接受 </link> 的模式,例如
- <literal>"dd.MM.yyyy HH:mm:ss"</literal> (这里
- <literal>"HH"</literal> 表示 0-23 时) 或
- <literal>"MM/dd/yyyy hh:mm:ss a"</literal> (如果当前语言是英语,这里
- <literal>"a"</literal> 输出 AM 或 PM)。</para>
- </listitem>
-
- <listitem>
- <para><literal>"xs"</literal> 就是XML Schema 格式,或
- <literal>"iso"</literal> 是 ISO 8601:2004 格式。
- 这些格式允许多个可选项,由空格分隔开,比如
- <literal>"iso m nz"</literal> (或者使用
- <literal>_</literal>,比如 <literal>"iso_m_nz"</literal>;
- 比如设置 <literal>lastModified?string.iso_m_nz</literal> 时就很有用)。
- 选项和它们的意义是:</para>
-
- <itemizedlist>
- <listitem>
- <para>精度选择:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>ms</literal>:毫秒,通常显示3位数字,
- 即便它们都是0。例如:<literal>13:45:05.800</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>s</literal>:秒(如果非0,小数部分就被丢弃了),
- 比如 <literal>13:45:05</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>m</literal>:分,比如
- <literal>13:45</literal>。它不允许用于
- <literal>"xs"</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>h</literal>:小时,比如
- <literal>13</literal>。它不允许用于
- <literal>"xs"</literal>。</para>
- </listitem>
-
- <listitem>
- <para>两者皆不:上至毫秒的精度,但是尾部的0毫秒就被移除了,
- 否则,如果它是0,整个毫秒部分也被移除。比如:
- <literal>13:45:05.8</literal></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>时区偏移可见性选项:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>fz</literal>: <quote>Force
- Zone</quote>,通常显示时区偏移(也对
- <literal>java.sql.Date</literal> 和
- <literal>java.sql.Time</literal> 值)。但是,
- 因为 ISO 8601 不允许日期(也就是没有时间的日期)显示时区偏移,
- 该选项对使用 <literal>"iso"</literal> 的日期就没有作用。</para>
- </listitem>
-
- <listitem>
- <para><literal>nz</literal>: <quote>No Zone</quote>,
- 从不显示时区偏移</para>
- </listitem>
-
- <listitem>
- <para>两者皆不:除了 <literal>java.sql.Date</literal> 和
- <literal>java.sql.Time</literal>,还有
- <literal>"iso"</literal> 日期值,通常都显示时区偏移。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>时区选项:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>u</literal>:使用 UTC 来代替
- <literal>time_zone</literal> 设置建议的内容。
- 然而,<literal>java.sql.Date</literal> 和
- <literal>java.sql.Time</literal> 不受它影响
- (参考 <literal>sql_date_and_time_time_zone</literal>
- 去理解为什么)</para>
- </listitem>
-
- <listitem>
- <para><literal>fu</literal>: <quote>Force
- UTC</quote>,也就是说,使用 UTC 来代替
- <literal>time_zone</literal> 或
- <literal>sql_date_and_time_time_zone</literal>
- 设置建议的内容。这会影响
- <literal>java.sql.Date</literal> 和
- <literal>java.sql.Time</literal> 值。</para>
- </listitem>
-
- <listitem>
- <para>两者皆不:使用
- <literal>time_zone</literal> 或
- <literal>sql_date_and_time_time_zone</literal>
- 配置设置项建议的时区。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
-
- <para>来自相同分类的选项是互斥的,比如一起使用
- <literal>m</literal> 和 <literal>s</literal>
- 就会有错误。</para>
-
- <para>选项可以指定一个任意的顺序。</para>
-
- <para>精度和时区偏移可见性选项不影响解析,只影响格式化。例如,
- 即使使用 <literal>"iso m nz"</literal>,
- <literal>"2012-01-01T15:30:05.125+01"</literal>
- 也会被成功解析含有毫秒精度。仅当解析不包含时区偏移的字符串时,
- 时区选项(比如 <literal>"u"</literal>) 影响选择的时区。</para>
-
- <para>使用 <literal>"iso"</literal> 解析会理解
- <quote>extend format</quote> 和 <quote>basic
- format</quote>,比如 <literal>20141225T235018</literal>。
- 它不支持所有的 ISO 8601 字符串:如果有日期部分,
- 必须使用年,月和日值(不是年中的星期),并且日不能被忽略。</para>
-
- <para><literal>"iso"</literal> 的输出是有意的,
- 所以它也是有 XML Schema 格式值的很好表述,除了0和负数的年,
- 这里是不可能的。也请注意,时区偏移在 <literal>"iso"</literal>
- 格式中是忽略的,而在 <literal>"xs"</literal> 格式中是保留的。</para>
- </listitem>
-
- <listitem>
- <para><literal>"short"</literal>,
- <literal>"medium"</literal>, <literal>"long"</literal>,或
- <literal>"full"</literal>,这些由Java平台定义,有本地依赖含义
- (参考 <link
- xlink:href="http://docs.oracle.com/javase/7/docs/api/java/text/DateFormat.html">
- <literal>java.text.DateFormat</literal> 的文档</link>)。
- 对于日期-时间值,可以分别指定日期和时间部分的长度,使用
- <literal>_</literal> 将它们分开,比如 <literal>"short_medium"</literal>。
- (对于时间-日期值,<literal>"medium"</literal> 表示
- <literal>"medium_medium"</literal>。)</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>time_zone</primary>
- </indexterm><literal>time_zone</literal>:时区的名称来显示并格式化时间。
- 默认情况下,使用JVM的时区。也可以是 <link
- xlink:href="http://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html">Java
- 时区 API</link> 接受的值,或者 <literal>"JVM default"</literal> (从
- FreeMarker 2.3.21 版本开始) 使用JVM默认的时区。比如:
- <literal>"GMT"</literal>, <literal>"GMT+2"</literal>,
- <literal>"GMT-1:30"</literal>, <literal>"CET"</literal>,
- <literal>"PST"</literal>,
- <literal>"America/Los_Angeles"</literal>。</para>
-
- <warning>
- <para>如果修改了该设置的默认值,那么也应该设置
- <literal>sql_date_and_time_time_zone</literal> 为 "JVM
- default"。<phrase role="forProgrammers">在
- <literal>Configurable.setSQLDateAndTimeTimeZone(TimeZone)</literal>
- 的Java API 文档中参考更多内容。</phrase></para>
- </warning>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>time_zone</primary>
- </indexterm><indexterm>
- <primary>JDBC time zone</primary>
- </indexterm><literal>sql_date_and_time_time_zone</literal>
- (从 FreeMarker 2.3.21 版本开始):它控制高度技术性的问题,
- 所以它应该由程序员在Java代码中来设置。
- <phrase role="forProgrammers">对于程序员:如果它设置为
- 非 <literal>null</literal>,对于来自SQL数据库(更精确地,就是
- <literal>java.sql.Date</literal> 和
- <literal>java.sql.Time</literal> 对象)的仅日期和仅时间值来说,
- FreeMarker 会使用该时区来代替由 <literal>time_zone</literal>
- 设置项指定的时区。在
- <literal>Configurable.setSQLDateAndTimeTimeZone(TimeZone)</literal>
- 的Java API文档中参考更多。
- </phrase></para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>url_escaping_charset</primary>
- </indexterm><literal>url_escaping_charset</literal>:
- 用来URL转义(比如<literal>${foo?url}</literal>)的字符集,
- 来计算转义(<literal>%<replaceable>XX</replaceable></literal>)的部分。
- 通常包含 FreeMarker 的框架应该设置它,所以不应该在模板中来设置。<phrase
- role="forProgrammers">(程序员可以在 <link
- linkend="pgui_misc_charset">这里...</link> 阅读更多内容。)</phrase></para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>output_encoding</primary>
- </indexterm><literal>output_encoding</literal>:告诉 FreeMarker
- 输出的字符集是什么。因为 FreeMarker 输出 UNICODE 字符集
- (<phrase role="forProgrammers">写入
- <literal>java.io.Writer</literal></phrase>)的流,它不由输出编码所影响,
- 但是一些宏/函数和内建函数也许想使用这些信息。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>classic_compatible</primary>
- </indexterm><literal>classic_compatible</literal>:这是对于专家来说的。
- 它的值应该是布尔值。参考
- <literal>freemarker.template.Configurable</literal> 的文档来获取更多信息。</para>
- </listitem>
- </itemizedlist>
-
- <para>比如:假设初始化的模板本地化是 de_DE
- (德国)。那么:</para>
-
- <programlisting role="template">${1.2}
-<#setting locale="en_US">
-${1.2}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">1,2
-1.2</programlisting>
-
- <para>因为德国人使用逗号作为小数分隔符,而美国人使用点。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_stop">
- <title>stop</title>
-
- <anchor xml:id="ref.directive.stop"/>
-
- <indexterm>
- <primary>stop directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#stop></literal>
-或
-<literal><#stop <replaceable>reason</replaceable>></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>reason</replaceable></literal>:
- 关于终止原因的信息化消息。是表达式,被算做是字符串。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>中止模板处理,给出(可选的)错误消息。
- <emphasis>不要在普通情况下对结束模板处理使用!</emphasis>
- FreeMarker 模板的调用者会将它视为失败的模板呈现,
- 而不是普通完成的。</para>
-
- <para><phrase role="forProgrammers">该指令抛出
- <literal>StopException</literal>,而且
- <literal>StopException</literal> 会持有 reason 参数的值。</phrase></para>
- </section>
- </section>
-
- <section xml:id="ref_directive_switch">
- <title>switch, case, default, break</title>
-
- <anchor xml:id="ref.directive.switch"/>
-
- <anchor xml:id="ref.directive.case"/>
-
- <anchor xml:id="ref.directive.default"/>
-
- <anchor xml:id="ref.directive.switch.break"/>
-
- <indexterm>
- <primary>switch directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>case directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>default directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>break directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#switch <replaceable>value</replaceable>>
- <#case <replaceable>refValue1</replaceable>>
- <replaceable>...</replaceable>
- <#break>
- <#case <replaceable>refValue2</replaceable>>
- <replaceable>...</replaceable>
- <#break>
- <replaceable>...</replaceable>
- <#case <replaceable>refValueN</replaceable>>
- <replaceable>...</replaceable>
- <#break>
- <#default>
- <replaceable>...</replaceable>
-</#switch>
-</literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>value</replaceable></literal>,
- <literal><replaceable>refValue1</replaceable></literal>,等:
- 表达式将会计算成相同类型的标量。</para>
- </listitem>
- </itemizedlist>
-
- <para><literal>break</literal> 和 <literal>default</literal> 是可选的。</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>这个指令的用法是不推荐的,因为向下通过的行为容易出错。使用 <link
- linkend="ref.directive.elseif"><literal>elseif</literal></link>来代替,
- 除非你想利用向下通过这种行为。</para>
-
- <para>Switch 被用来选择模板中的一个片段,如何选择依赖于表达式的值:</para>
-
- <programlisting role="template"><#switch animal.size>
- <#case "small">
- This will be processed if it is small
- <#break>
- <#case "medium">
- This will be processed if it is medium
- <#break>
- <#case "large">
- This will be processed if it is large
- <#break>
- <#default>
- This will be processed if it is neither
-</#switch></programlisting>
-
- <para>在 <literal>switch</literal> 中间必须有一个或多个
- <literal><#case <replaceable>value</replaceable>></literal>,
- 在所有 <literal>case</literal> 标签之后,有一个可选的
- <literal><#default></literal>。 当FreeMarker到达
- <literal>switch</literal> 指令时,它会选择一个
- <literal>case</literal> 指令,这里的
- <literal><replaceable>refValue</replaceable></literal> 等于
- <literal><replaceable>value</replaceable></literal> 并且继续模板处理。
- 如果没有和合适的值匹配的 <literal>case</literal> 指令,那么就继续处理
- <literal>default</literal> 指令,如果它存在,否则就继续处理
- <literal>switch</literal> 结束标签之后的内容。现在有一个混乱的事情:
- 当它选择一个 <literal>case</literal> 指令后,它就会继续处理其中的内容,
- 直到遇到 <literal>break</literal> 指令。也就是它遇到另外一个
- <literal>case</literal> 指令或 <literal><#default></literal>
- 标记时也不会自动离开 <literal>switch</literal> 指令。比如:</para>
-
- <programlisting role="template"><#switch x>
- <#case 1>
- 1
- <#case 2>
- 2
- <#default>
- d
-</#switch></programlisting>
-
- <para>如果 <literal>x</literal> 是 1,它会打印1 2 d;如果
- <literal>x</literal> 是 2,那么就会打印2 d;如果
- <literal>x</literal> 是 3,那么就会打印d。这就是前面提到的向下通过行为。
- <literal>break</literal> 标记指示 FreeMarker 直接略过剩下的
- <literal>switch</literal> 代码段。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_t">
- <title>t, lt, rt</title>
-
- <anchor xml:id="ref.directive.t"/>
-
- <anchor xml:id="ref.directive.lt"/>
-
- <anchor xml:id="ref.directive.rt"/>
-
- <indexterm>
- <primary>t directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>lt directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>rt directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>trimmer directives</primary>
- </indexterm>
-
- <indexterm>
- <primary>white-space removal</primary>
-
- <secondary>trimming</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><#t></literal>
-
-<literal><#lt></literal>
-
-<literal><#rt></literal></programlisting>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>这些指令,指示FreeMarker去忽略标记中行的特定的空白:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>t</literal> (整体削减):忽略本行中首和尾的所有空白。</para>
- </listitem>
-
- <listitem>
- <para><literal>lt</literal> (左侧削减):忽略本行中首部所有的空白。</para>
- </listitem>
-
- <listitem>
- <para><literal>rt</literal> (右侧削减):忽略本行中尾部所有的空白。</para>
- </listitem>
- </itemizedlist>
-
- <para>这里:</para>
-
- <itemizedlist>
- <listitem>
- <para>"首部空白" 表示本行所有空格和制表符
- (和其他根据 <link
- linkend="gloss.unicode">UNICODE</link> 中的空白字符,除了<link
- linkend="gloss.lineBreak">换行符</link>)
- 在第一个非空白字符之前。</para>
- </listitem>
-
- <listitem>
- <para>"尾部空白" 表示本行所有的空格和制表符
- (和其他根据 <link
- linkend="gloss.unicode">UNICODE</link> 中的空白字符,除了换行符)
- 在最后一个非空白字符之后,<emphasis>还有</emphasis> 行末尾的换行符。</para>
- </listitem>
- </itemizedlist>
-
- <para>理解这些检查模板本身的指令是很重要的,而 <emphasis>不是</emphasis>
- 当你合并数据模型时,模板生成的输出。<phrase role="forProgrammers">
- (也就是说,空白的移除发生在解析阶段。)</phrase></para>
-
- <para>例如:</para>
-
- <programlisting role="template">--
- 1 <#t>
- 2<#t>
- 3<#lt>
- 4
- 5<#rt>
- 6
---
-</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">--
-1 23
- 4
- 5 6
---</programlisting>
-
- <para>这些指令在行内的放置不重要。也就是说,不管你是将它们放在行的开头,
- 或是行的末尾,或是在行的中间,效果都是一样的。</para>
- </section>
- </section>
-
- <section xml:id="ref_directive_userDefined">
- <title>User-defined directive (<@...>)</title>
-
- <anchor xml:id="ref.directive.userDefined"/>
-
- <indexterm>
- <primary>user-defined directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><@<replaceable>user_def_dir_exp</replaceable> <replaceable>param1</replaceable>=<replaceable>val1</replaceable> <replaceable>param2</replaceable>=<replaceable>val2</replaceable> <replaceable>...</replaceable> <replaceable>paramN</replaceable>=<replaceable>valN</replaceable>/></literal>
-(注意 XML 风格, <literal>/</literal> 在 <literal>></literal> 之前)
-或如果需要循环变量 (<link
- linkend="ref_directive_userDefined_loopVar">更多细节...</link>)
-<literal><@<replaceable>user_def_dir_exp</replaceable> <replaceable>param1</replaceable>=<replaceable>val1</replaceable> <replaceable>param2</replaceable>=<replaceable>val2</replaceable> <replaceable>...</replaceable> <replaceable>paramN</replaceable>=<replaceable>valN</replaceable> ; <replaceable>lv1</replaceable>, <replaceable>lv2</replaceable>, <replaceable>...</replaceable>, <replaceable>lvN</replaceable>/></literal>
-
-或和上面两个相同但是使用结束标签 (<link
- linkend="ref_directive_userDefined_entTag">更多细节...</link>):
-
-<literal><@<replaceable>user_def_dir_exp</replaceable> <replaceable>...</replaceable>>
- ...
-</@<replaceable>user_def_dir_exp</replaceable>></literal>
-或
-<literal><@<replaceable>user_def_dir_exp</replaceable> <replaceable>...</replaceable>>
- ...
-</@></literal>
-
-或和上面的相同但是使用位置参数传递 (<link
- linkend="ref_directive_userDefined_positionalParam">更多细节...</link>):
-
-<literal><@<replaceable>user</replaceable> <replaceable>val1</replaceable>, <replaceable>val2</replaceable>, <replaceable>...</replaceable>, <replaceable>valN</replaceable>/></literal>
-等...
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>user_def_dir_exp</replaceable></literal>:
- 表达式算作是自定义指令(比如宏),将会被调用。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>param1</replaceable></literal>,
- <literal><replaceable>param2</replaceable></literal>等...:
- 参数的名称,它们 <emphasis>不是</emphasis> 表达式。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>val1</replaceable></literal>,
- <literal><replaceable>val2</replaceable></literal>等...:
- 参数的值,它们 <emphasis>是</emphasis> 表达式。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>lv1</replaceable></literal>,
- <literal><replaceable>lv2</replaceable></literal>等...:
- <link linkend="dgui_misc_var">循环变量</link> 的名称,
- 它们 <emphasis>不是</emphasis> 表达式。</para>
- </listitem>
- </itemizedlist>
-
- <para>参数的数量可以是0(也就是没有参数)。</para>
-
- <para>参数的顺序并不重要(除非你使用了位置参数传递)。
- 参数名称必须唯一。在参数名中小写和大写的字母被认为是不同的字母
- (也就是 <literal>Color</literal> 和 <literal>color</literal> 是不同的)。</para>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>这将调用用户自定义指令,比如宏。参数的含义,
- 支持和需要的参数的设置依赖于具体的自定义指令。</para>
-
- <para>你可以阅读 <link linkend="dgui_misc_userdefdir">自定义指令</link>。</para>
-
- <para>示例1:调用存储在变量
- <literal>html_escape</literal> 中的指令:</para>
-
- <programlisting role="template"><@html_escape>
- a < b
- Romeo & Juliet
-</@html_escape></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> a &lt; b
- Romeo &amp; Juliet</programlisting>
-
- <para>示例2:调用有参数的宏:</para>
-
- <programlisting role="template"><@list items=["mouse", "elephant", "python"] title="Animals"/>
-<replaceable>...</replaceable>
-<#macro list title items>
- <p>${title?cap_first}:
- <ul>
- <#list items as x>
- <li>${x?cap_first}
- </#list>
- </ul>
-</#macro></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>Animals:
- <ul>
- <li>Mouse
- <li>Elephant
- <li>Python
- </ul>
-
-<replaceable>...</replaceable></programlisting>
-
- <section xml:id="ref_directive_userDefined_entTag">
- <title>结束标签</title>
-
- <para>你可以在 <link linkend="gloss.endTag">结束标签</link> 中忽略
- <literal><replaceable>user_def_dir_exp</replaceable></literal>。
- 也就是说,你可以写 <literal></@></literal> 来替代
- <literal></@<replaceable>anything</replaceable>></literal>。
- 这个规则当表达式 <literal><replaceable>user_def_dir_exp</replaceable></literal>
- 太复杂时非常有用,因为你不需要在结束标签中重复表达式。
- 此外,如果表达式包含比简单变量名和点还多的表达式,你就不能再重复它们了。比如,
- <literal><@a_hash[a_method()]><replaceable>...</replaceable></@a_hash[a_method()]></literal> 就是错的,你必须写为
- <literal><@a_hash[a_method()]><replaceable>...</replaceable></@></literal>。
- 但是
- <literal><@a_hash.foo><replaceable>...</replaceable></@a_hash.foo></literal>
- 是可以的。</para>
- </section>
-
- <section xml:id="ref_directive_userDefined_loopVar">
- <title>循环变量</title>
-
- <para>一些自定义指令创建循环变量(和 <literal>list</literal> 指令相似)。
- 正如预定义指令(如 <literal>list</literal>)一样,当你调用这个指令
- (如 <literal><#list foos as
- foo><replaceable>...</replaceable></#list></literal>中的
- <literal>foo</literal>)时循环变量的 <emphasis>名称</emphasis> 就给定了,
- 而变量的 <emphasis>值</emphasis> 是由指令本身设置的。
- 在自定义指令的情形下,语法是循环变量的名称在分号之后给定。比如:</para>
-
- <programlisting role="template"><@myRepeatMacro count=4 ; <emphasis>x, last</emphasis>>
- ${<emphasis>x</emphasis>}. Something... <#if <emphasis>last</emphasis>> This was the last!</#if>
-</@myRepeatMacro></programlisting>
-
- <para>请注意,由自定义指令创建的循环变量数量和分号之后指定的循环变量数量需要不匹配。
- 也就是说,如果你对重复是否是最后一个不感兴趣,你可以简单来写:</para>
-
- <programlisting role="template"><@myRepeatMacro count=4 ; <emphasis>x</emphasis>>
- ${<emphasis>x</emphasis>}. Something...
-</@myRepeatMacro></programlisting>
-
- <para>或者你可以:</para>
-
- <programlisting role="template"><@myRepeatMacro count=4>
- Something...
-</@myRepeatMacro></programlisting>
-
- <para>此外,如果你在分号之后指定更多循环变量而不是自定义指令创建的,
- 也不会引起错误,只是最后的循环变量不能被创建
- (也就是在嵌套内容中那些将是未定义的)。尝试使用未定义的循环变量,
- 就会引起错误(除非你使用如 <literal>?default</literal> 这样的内建函数),
- 因为你尝试访问了一个不存在的变量。</para>
-
- <para>参考 <link linkend="dgui_misc_userdefdir">自定义指令</link> 来获取更多内容。</para>
- </section>
-
- <section xml:id="ref_directive_userDefined_positionalParam">
- <title>位置参数传递</title>
-
- <indexterm>
- <primary>positional parameter passing</primary>
- </indexterm>
-
- <para>位置参数传递(如<literal><@heading "Preface", 1/></literal>)
- 是正常命名参数传递(如<literal><@heading title="Preface" level=1/></literal>)
- 的速记形式,这里忽略了参数的名称。如果自定义指令只有一个参数,
- 或者对于经常使用的自定义指令它参数的顺序很好记忆,速记形式应该被应用。
- 为了应用这种形式,你不得不了解声明的命名参数的顺序(如果指令只有一个参数这是很琐碎的)。
- 也就是,如果 <literal>heading</literal> 被创建为 <literal><#macro heading title
- level><replaceable>...</replaceable></literal>,
- 那么 <literal><@heading "Preface", 1/></literal> 和
- <literal><@heading title="Preface" level=1/></literal>
- (或 <literal><@heading level=1 title="Preface"/></literal>;
- 如果你使用参数名称,那顺序就不重要了)是相等的。
- 要注意位置参数传递现在仅仅支持宏定义。</para>
- </section>
- </section>
- </section>
-
- <section xml:id="ref_directive_visit">
- <title>visit, recurse, fallback</title>
-
- <anchor xml:id="ref.directive.visit"/>
-
- <indexterm>
- <primary>visit directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>recurse directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>fallback directive</primary>
- </indexterm>
-
- <indexterm>
- <primary>recursion</primary>
-
- <secondary>iterate</secondary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#visit <replaceable>node</replaceable> using <replaceable>namespace</replaceable>></literal>
-或
-<literal><#visit <replaceable>node</replaceable>></literal></programlisting>
-
- <programlisting role="metaTemplate"><literal><#recurse <replaceable>node</replaceable> using <replaceable>namespace</replaceable>></literal>
-或
-<literal><#recurse <replaceable>node</replaceable>></literal>
-或
-<literal><#recurse using <replaceable>namespace</replaceable>></literal>
-或
-<literal><#recurse></literal></programlisting>
-
- <programlisting role="metaTemplate"><literal><#fallback></literal></programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>node</replaceable></literal>:
- 算作 <link linkend="xgui_expose_dom">结点变量</link> 的表达式。</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>namespace</replaceable></literal>:
- <link linkend="dgui_misc_namespace">命名空间</link>,或者是命名空间的序列。
- 命名空间可以以命名空间哈希表(又称为根哈希表)给定,
- 或者可以引入一个存储模板路径的字符串。代替命名空间哈希表,
- 你也可以使用普通哈希表。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para><literal>visit</literal> 和 <literal>recurse</literal>
- 指令是用来递归处理树的。在实践中,这通常被用来 <link
- linkend="xgui">处理XML</link>。</para>
-
- <section>
- <title>Visit</title>
-
- <para>当你调用了 <literal><#visit <replaceable>node</replaceable>></literal>时,
- 它看上去像用户自定义指令(比如宏)来调用从结点名称
- (<literal><replaceable>node</replaceable>?node_name</literal>)
- 和命名空间
- (<literal><replaceable>node</replaceable>?node_namespace</literal>)
- 中有名称扣除的结点。名称扣除的规则:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果结点不支持结点命名空间(如XML中的文本结点),
- 那么这个指令名仅仅是结点的名称
- (<literal><replaceable>node</replaceable>?node_name</literal>)。
- <phrase role="forProgrammers">如果 <literal>getNodeNamespace</literal>
- 方法返回 <literal>null</literal> 时结点就不支持结点命名空间了。</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果结点支持结点命名空间(如XML中的元素结点),
- 那么从结点命名空间中的前缀扣除可能在结点名称前和一个做为分隔符
- (比如 <literal>e:book</literal>)的冒号追加上去。前缀,以及是否使用前缀,
- 依赖于何种前缀 <link linkend="dgui_misc_namespace">FTL命名空间</link> 中用
- <literal>ftl</literal> 指令的 <literal>ns_prefixes</literal> 参数注册的,
- 那里 <literal>visit</literal> 寻找控制器指令
- (<literal>visit</literal> 调用的相同FTL命名空间不是重要的,后面你将会看到)。
- 具体来说,如果没有用 <literal>ns_prefixes</literal> 注册默认的命名空间,
- 那么对于不属于任何命名空间(<phrase
- role="forProgrammers">当 <literal>getNodeNamespace</literal>
- 返回 <literal>""</literal></phrase>)的结点来说就不使用前缀。
- 如果使用 <literal>ns_prefixes</literal> 给不属于任意命名空间的结点注册了默认命名空间,
- 那么就使用前缀 <literal>N</literal>,而对于属于默认结点命名空间的结点就不使用前缀了。
- 否则,这两种情况下,用 <literal>ns_prefixes</literal> 关联结点命名空间的前缀已经被使用了。
- 如果没有关联结点命名空间的结点前缀,那么 <literal>visit</literal>
- 仅仅就好像没有以合适的名称发现指令。</para>
- </listitem>
- </itemizedlist>
-
- <para>自定义指令调用的结点对于特殊变量 <literal>.node</literal> 是可用的。比如:</para>
-
- <programlisting role="template"><#-- Assume that nodeWithNameX?node_name is "x" -->
-<#visit nodeWithNameX>
-Done.
-<#macro x>
- Now I'm handling a node that has the name "x".
- Just to show how to access this node: this node has ${.node?children?size} children.
-</#macro></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> Now I'm handling a node that has the name "x".
- Just to show how to access this node: this node has 3 children.
-Done.</programlisting>
-
- <para>如果使用可选的 <literal>using</literal> 从句来指定一个或多个命名空间,
- 那么 <literal>visit</literal> 就会在那么命名空间中寻找指令,
- 和先前列表中指定的命名空间都获得优先级。如果指定 <literal>using</literal> 从句,
- 对最后一个未完成的 <literal>visit</literal> 调用的用 <literal>using</literal>
- 从句指定命名空间的命名空间或序列被重用了。如果没有这样挂起的
- <literal>visit</literal> 调用,那么当前的命名空间就被使用。
- 比如,如果你执行这个模板:</para>
-
- <programlisting role="template"><#import "n1.ftl" as n1>
-<#import "n2.ftl" as n2>
-
-<#-- This will call n2.x (because there is no n1.x): -->
-<#visit nodeWithNameX using [n1, n2]>
-
-<#-- This will call the x of the current namespace: -->
-<#visit nodeWithNameX>
-
-<#macro x>
- Simply x
-</#macro></programlisting>
-
- <para>这是 <literal>n1.ftl</literal>:</para>
-
- <programlisting role="template"><#macro y>
- n1.y
-</#macro></programlisting>
-
- <para>这是 <literal>n2.ftl</literal>:</para>
-
- <programlisting role="template"><#macro x>
- n2.x
- <#-- This callc n1.y as it inherits the "using [n1, n2]" from the pending visit call: -->
- <#visit nodeWithNameY>
- <#-- This will call n2.y: -->
- <#visit nodeWithNameY using .namespace>
-</#macro>
-
-<#macro y>
- n2.y
-</#macro></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">
- n2.x
- n1.y
- n2.y
-
- Simply x
- </programlisting>
-
- <para>如果 <literal>visit</literal>
- 既没有在和之前描述规则的名称扣除相同名字的FTL命名空间发现自定义指令,
- 那么它会尝试用名称 <literal>@<replaceable>node_type</replaceable></literal> 查找,
- 又如果结点不支持结点类型属性
- (也就是 <literal><replaceable>node</replaceable>?node_type</literal> 返回未定义变量),
- 那么使用名称 <literal>@default</literal>。对于查找来说,它使用和之前描述相同的机制。
- 如果仍然没有找到处理结点的自定义指令,那么 <literal>visit</literal> 停止模板执行,
- 并抛出错误。一些XML特定的结点类型在这方面有特殊的处理;
- 参考:<xref linkend="xgui_declarative_details"/>。比如:</para>
-
- <programlisting role="template"><#-- Assume that nodeWithNameX?node_name is "x" -->
-<#visit nodeWithNameX>
-
-<#-- Assume that nodeWithNameY?node_type is "foo" -->
-<#visit nodeWithNameY>
-
-<#macro x>
-Handling node x
-</#macro>
-
-<#macro @foo>
-There was no specific handler for node ${node?node_name}
-</#macro></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">Handling node x
-
-There was no specific handler for node y
-
- </programlisting>
- </section>
-
- <section>
- <title>Recurse</title>
-
- <anchor xml:id="ref.directive.recurse"/>
-
- <para><literal><#recurse></literal> 指令是真正纯语义上的指令。
- 它访问结点的所有子结点(而没有结点本身)。所以来写:</para>
-
- <programlisting role="template"><#recurse <replaceable>someNode</replaceable> using <replaceable>someLib</replaceable>></programlisting>
-
- <para>和这个是相等的:</para>
-
- <programlisting role="template"><#list <replaceable>someNode</replaceable>?children as <replaceable>child</replaceable>><#visit <replaceable>child</replaceable> using <replaceable>someLib</replaceable>></#list></programlisting>
-
- <para>而目标结点在 <literal>recurse</literal> 指令中是可选的。
- 如果目标结点没有指定,那就仅仅使用 <literal>.node</literal>。
- 因此,<literal><#recurse></literal> 这个精炼的指令和下面这个是相同的:</para>
-
- <programlisting role="template"><#list .node?children as child><#visit child></#list></programlisting>
-
- <para>对于熟悉XSLT的用户的评论,<literal><#recurse></literal>
- 是和XSLT中 <literal><xsl:apply-templates/></literal> 指令相当类似的。</para>
- </section>
-
- <section>
- <title>Fallback</title>
-
- <anchor xml:id="ref.directive.fallback"/>
-
- <para>正如前面所学的,在 <literal>visit</literal> 指令的文档中,
- 自定义指令控制的结点也许在多个FTL命名空间中被搜索。
- <literal>fallback</literal> 指令可以被用在自定义指令中被调用处理结点。
- 它指挥 FreeMarker 在更多的命名空间
- (也就是,在当前调用列表中自定义指令命名空间之后的命名空间)
- 中来继续搜索自定义指令。如果结点处理器被发现,
- 那么就被调用,否则 <literal>fallback</literal> 不会做任何事情。</para>
-
- <para>这个指令的典型用法是在处理程序库之上写定制层,有时传递控制到定制的库中:</para>
-
- <programlisting role="template"><#import "/lib/docbook.ftl" as docbook>
-
-<#--
- We use the docbook library, but we override some handlers
- in this namespace.
--->
-<#visit document using [.namespace, docbook]>
-
-<#--
- Override the "programlisting" handler, but only in the case if
- its "role" attribute is "java"
--->
-<#macro programlisting>
- <#if .node.@role[0]!"" == "java">
- <#-- Do something special here... -->
- ...
- <#else>
- <#-- Just use the original (overidden) handler -->
- <#fallback>
- </#if>
-</#macro></programlisting>
- </section>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="ref_specvar">
- <title>特殊变量参考</title>
-
- <indexterm>
- <primary>special variable</primary>
- </indexterm>
-
- <para>特殊变量是由FreeMarker引擎自己定义的变量。要访问它们,
- 你可以使用 <literal>.<replaceable>variable_name</replaceable></literal> 语法。
- 比如,你不能仅仅写 <literal>version</literal>,而必须写<literal>.version</literal>。</para>
-
- <note>
- <para>在 FreeMarker 2.3.23 版本中,可以使用驼峰格式的特殊变量名来代替蛇形格式,
- 比如 <literal>dataModel</literal> 代替 <literal>data_model</literal>。
- 但是需要知道在相同的模板中,FreeMarker 会对模板语言部分的标识符强制使用驼峰格式
- (用户自定义名称是无效的)。</para>
- </note>
-
- <para>支持的特殊变量有:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><indexterm>
- <primary>current_template_name</primary>
- </indexterm><literal>current_template_name</literal><literal/>:
- 当前所在的模板名称(从 FreeMarker 2.3.23 版本开始可用)。
- 如果模板是在Java中 <phrase
- role="forProgrammers">(通过 <literal>new Template(null,
- <replaceable>...</replaceable>)</literal>)</phrase> 动态创建的,
- 而不是从后台通过名称 <phrase
- role="forProgrammers">(通过
- <literal><replaceable>cfg</replaceable>.getTemplate(name,
- <replaceable>...</replaceable>)</literal>)</phrase >加载的。
- 那么它可以为空(<literal>null</literal>)。
- 迁移说明:如果使用它来替换废弃的 <literal>template_name</literal>,
- 请注意,如果模板没有名字,那么它是一个长度为0的字符串,而不是空
- (<literal>null</literal>),所以可以在遗留的模板中编写
- <literal>current_template_name!''</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>data_model</literal>:可以用来直接访问数据模型的哈希表。
- 也就是,使用 <literal>global</literal> 指令定义在这里不可见的的变量。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>error</primary>
- </indexterm><literal>error</literal> (从 FreeMarker 2.3.1 版本开始可用):
- 该变量在 <link linkend="ref.directive.attempt"><literal>recover</literal>
- 指令</link> 体中可以访问,它存储了我们要恢复错误的信息。</para>
- </listitem>
-
- <listitem>
- <para><literal>globals</literal>:可以用来访问全局可访问的变量的哈希表:
- 数据模型和由 <literal>global</literal> 指令创建的变量。请注意,用
- <literal>assign</literal> 或 <literal>macro</literal> 创建的变量不是全局的。
- 因此当你使用 <literal>globals</literal> 时你不能隐藏变量。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>language</primary>
- </indexterm><indexterm>
- <primary>lang</primary>
- </indexterm><literal>lang</literal>:返回当前本地化设置的语言部分的值。
- 比如 <literal>.locale</literal> 是 <literal>en_US</literal>,
- 那么 <literal>.lang</literal> 是 <literal>en</literal>。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>locale</primary>
- </indexterm><literal>locale</literal>:返回当前本地化设置的值。
- 这是一个字符串,比如 <literal>en_US</literal>。
- 要获取关于本地化字符串值的更多内容,<link linkend="ref.directive.setting">请参考
- <literal>setting</literal> 指令</link>。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>locale_object</primary>
- </indexterm><literal>locale_object</literal> (从 FreeMarker 2.3.21 版本开始可用):
- 以 <literal>java.util.Locale</literal> 对象返回本地化设置的当前值,而不是字符串。
- 也就是说,当你想要传递一个 <literal>java.util.Locale</literal> 对象给Java方法时,
- 它可以用于代替 <literal>.locale</literal>。(<literal>Locale</literal> 对象会根据
- <literal>object_wrapper</literal> 设置项的值来被包装。是否真的可以以
- <literal>Locale</literal> 对象传递该值给Java方法依赖于对象包装器,
- 但是对象包装器允许你直接调用Java方法不太可能不支持它。)</para>
- </listitem>
-
- <listitem>
- <para><literal>locals</literal>:你可以访问局部变量的哈希表
- (由 <literal>local</literal> 指令创建的变量,还有宏的参数)。</para>
- </listitem>
-
- <listitem>
- <para><literal>main</literal>:可以用来访问主<link
- linkend="dgui_misc_namespace">命名空间</link>的哈希表。
- 请注意,像数据模型中的全局变量通过这个哈希表是
- <emphasis>不</emphasis> 可见的。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>main_template_name</primary>
- </indexterm><literal>main_template_name</literal>:顶级模板的名称
- (从 FreeMarker 2.3.23 版本开始可用)。<phrase
- role="forProgrammers">(在Java中,这就是
- <literal>Template.process</literal> 调用的模板) </phrase>
- 如果模板是在Java中 <phrase role="forProgrammers">(通过 <literal>new
- Template(null, <replaceable>...</replaceable>)</literal>)</phrase>
- 动态创建出来的,而不是从存储器中以名称加载<phrase
- role="forProgrammers">(通过
- <literal><replaceable>cfg</replaceable>.getTemplate(name,
- <replaceable>...</replaceable>)</literal>)</phrase>的,
- 那么它可以不存在(<literal>null</literal>)。迁移说明:
- 如果使用它来替换废弃的 <literal>template_name</literal>,
- 请注意,如果模板没有名字,那么它是一个长度为0的字符串,而不是空
- (<literal>null</literal>),所以可以在遗留的模板中编写
- <literal>current_template_name!''</literal>。</para>
- </listitem>
-
- <listitem>
- <para><literal>namespace</literal>:可以用来访问当前 <link
- linkend="dgui_misc_namespace">命名空间</link> 的哈希表。
- 请注意,像数据模型中的全局变量通过这个哈希表是
- <emphasis>不</emphasis> 可见的。</para>
- </listitem>
-
- <listitem>
- <para><literal>node</literal> (由于历史原因重命名为
- <literal>current_node</literal>):可以用访问者模式(也就是用 <link
- linkend="ref_directive_visit"><literal>visit</literal>,
- <literal>recurse</literal> 等指令</link>)处理的当前结点。
- 而且,当使用 <link
- linkend="pgui_misc_ant">FreeMarker XML Ant 任务</link> 时,
- 它初始存储根结点。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>now</primary>
- </indexterm><indexterm>
- <primary>current date-time</primary>
- </indexterm><literal>now</literal>:返回当前的日期-时间。使用示例:
- "<literal>Page generated: ${.now}</literal>",
- "<literal>Today is ${.now?date}</literal>", "<literal>The current
- time is ${.now?time}</literal>"。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>output encoding</primary>
- </indexterm><indexterm>
- <primary>output charset</primary>
- </indexterm><literal>output_encoding</literal>
- (从 FreeMarker 2.3.1 版本开始可用):返回当前输出字符集的名称。
- 如果框架封装 FreeMarker 却没有为 FreeMarker 指定输出字符集时这个特殊变量是不存在的。
- <phrase role="forProgrammers">(程序员可以在 <link
- linkend="pgui_misc_charset">这里...</link> 阅读关于字符集问题的更多内容。)</phrase></para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>template_name</primary>
- </indexterm><literal>template_name</literal>:<emphasis>不要使用它,
- 因为当使用宏时,它的行为是很奇怪的;使用
- <literal>current_template_name</literal> 或
- <literal>main_template_name</literal> 来代替(参考那里的迁移说明)。</emphasis>
- 给出主模板的名字,如果我们运行包含或导入的模板,那么就是它们的名字。
- 当调用宏时,那就有些困惑了:宏调用不改变该特殊变量的值,但是当
- <literal>nested</literal> 被调用时,它会变成模板所属当前命名空间的名字。
- (从FreeMarker 2.3.14 版本开始可用)</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>URL escaping charset</primary>
- </indexterm><indexterm>
- <primary>URL escaping charset</primary>
- </indexterm><literal>url_escaping_charset</literal>
- (从 FreeMarker 2.3.1 版本开始可用):如果存在,
- 它存储了应该用于URL转义的字符集的名称。
- 如果这个变量不存在就意味着没有人指定URL编码应该使用什么样的字符集。
- 这种情况下,<link
- linkend="ref_builtin_url"><literal>url</literal> 内建函数</link>
- 使用特殊变量 <literal>output_encoding</literal> 指定的字符集来进行URL编码。
- 处理机制和它是相同的。<phrase role="forProgrammers">(程序员可以在 <link
- linkend="pgui_misc_charset">这里...</link> 阅读关于字符集问题的更多内容。)</phrase></para>
- </listitem>
-
- <listitem>
- <para><literal>vars</literal>:表达式 <literal>.vars.foo</literal>
- 返回和表达式 <literal>foo</literal> 相同的变量。
- 出于某些原因你不得不使用方括号语法时这是有用的,因为它只对哈希表子变量有用,
- 所以你需要一个人工创建的父哈希表。比如,要读取有特殊名称的顶层变量可能会把
- FreeMarker 弄糊涂,你可以写 <literal>.vars["A strange name!"]</literal>。
- 或者,使用和变量 <literal>varName</literal> 给定的动态名称访问顶层变量你可以写
- <literal>.vars[varName]</literal>。请注意,这个哈希表由 <literal>.vars</literal>
- 返回,并不支持 <literal>?keys</literal> 和 <literal>?values</literal>。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>version</primary>
- </indexterm><literal>version</literal>:返回 FreeMarker 版本号的字符串形式,
- 比如 <literal>2.2.8</literal>。这可以用来检查你的应用程序使用的是哪个版本的
- FreeMarker,但是要注意这个特殊变量在 2.3.0 或 2.2.8 版本之前不存在。
- 非最终发行版本号在数字包含破折号和进一步信息,比如
- 2.3.21-nightly_20140726T151800Z.</para>
- </listitem>
- </itemizedlist>
- </chapter>
-
- <chapter xml:id="ref_reservednames">
- <title>FTL 中的保留名称</title>
-
- <indexterm>
- <primary>reserved name</primary>
- </indexterm>
-
- <para>下面的这些名称不能在非方括号语法中被用作顶层变量
- (比如 <literal>.vars["in"]</literal>),因为这是FTL中的关键字:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal>true</literal>:布尔值"true"</para>
- </listitem>
-
- <listitem>
- <para><literal>false</literal>:布尔值"false"</para>
- </listitem>
-
- <listitem>
- <para><literal>gt</literal>:比较运算符"大于"</para>
- </listitem>
-
- <listitem>
- <para><literal>gte</literal>:比较运算符"大于或等于"</para>
- </listitem>
-
- <listitem>
- <para><literal>lt</literal>:比较运算符"小于"</para>
- </listitem>
-
- <listitem>
- <para><literal>lte</literal>:比较运算符"小于或等于"</para>
- </listitem>
-
- <listitem>
- <para><literal>as</literal>:由少数指令使用</para>
- </listitem>
-
- <listitem>
- <para><literal>in</literal>:由少数指令使用</para>
- </listitem>
-
- <listitem>
- <para><literal>using</literal>:由少数指令使用</para>
- </listitem>
- </itemizedlist>
- </chapter>
-
- <chapter xml:id="ref_deprecated">
- <title>废弃的 FTL 结构</title>
-
- <indexterm>
- <primary>deprecated</primary>
- </indexterm>
-
- <section xml:id="ref_depr_directive">
- <title>废弃的指令列表</title>
-
- <para>下面这些指令是废弃的,但是仍然可以运行:</para>
-
- <itemizedlist>
- <listitem>
- <para><link
- linkend="ref.directive.call"><literal>call</literal></link>:使用
- <link linkend="ref.directive.userDefined">自定义指令调用</link> 来代替。</para>
- </listitem>
-
- <listitem>
- <para><literal>comment</literal>:这是
- <literal><#--<replaceable>...</replaceable>--></literal> 的老格式。
- 在 <literal><#comment></literal> 和
- <literal></#comment></literal> 之间的任何东西都会被忽略。</para>
- </listitem>
-
- <listitem>
- <para><literal>foreach</literal>: 它是 <literal>list</literal>
- 指令的代名词,有着略微不同的参数语法。它的语法结构是 <literal><#foreach
- <replaceable>item</replaceable> in
- <replaceable>sequence</replaceable>></literal>,和 <literal><#list
- <replaceable>sequence</replaceable> as
- <replaceable>item</replaceable>></literal>是相同的。</para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.transform"><literal>transform</literal></link>:
- 使用 <link linkend="ref.directive.userDefined">自定义指令调用</link> 来代替</para>
- </listitem>
- </itemizedlist>
-
- <para>下面这些指令不再可以运行:</para>
-
- <itemizedlist>
- <listitem>
- <para>遗留的 <literal>function</literal>:起初 <literal>function</literal>
- 是被用作定义宏,现在由 <literal>macro</literal> 指令完成,它就被废弃了。
- 对于 FreeMarker 2.3 版本来说,这个指令由于不同的意义而再次引入:
- 它被用来定义方法。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_depr_builtin">
- <title>废弃的内建函数列表</title>
-
- <para>下面这些内建函数是被废弃的,但是仍可以运行:</para>
-
- <itemizedlist>
- <listitem>
- <para><indexterm>
- <primary>default built-in</primary>
- </indexterm> <literal>default</literal>:由于 <link
- linkend="dgui_template_exp_missing_default">默认值操作符</link> 的引入,它被废弃了。<literal><replaceable>exp1</replaceable>?default(<replaceable>exp2</replaceable>)</literal>
- 和
- <literal><replaceable>exp1</replaceable>!<replaceable>exp2</replaceable></literal>
- 是相同的,
- <literal>(<replaceable>exp1</replaceable>)?default(<replaceable>exp2</replaceable>)</literal>
- 和
- <literal>(<replaceable>exp1</replaceable>)!<replaceable>exp2</replaceable></literal> 是相同的。
- 唯一的不同是在 FreeMarker 2.4 版本之前,内建函数 <literal>default</literal> 通常算作是
- <literal><replaceable>exp2</replaceable></literal>,而默认值操作符仅仅当默认值真的需要时才算。
- 从 FreeMarker 2.4 版本之后,内建函数 <literal>default</literal> 被改进了,
- 和默认值运算符的行为非常像了。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>exists built-in</primary>
- </indexterm><literal>exists</literal>:由于 <link
- linkend="dgui_template_exp_missing_test">空值测试操作符</link> 的引入,它被废弃了。
- <literal><replaceable>exp1</replaceable>?exists</literal> 和
- <literal><replaceable>exp1</replaceable>??</literal> 是一样的,
- <literal>(<replaceable>exp1</replaceable>)?exists</literal> 和
- <literal>(<replaceable>exp1</replaceable>)??</literal> 也是一样的。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>if_exists built-in</primary>
- </indexterm><literal>if_exists</literal>:由于 <link
- linkend="dgui_template_exp_missing_default">默认值操作符</link> 的引入,它被废弃了。
- <literal><replaceable>exp1</replaceable>?if_exists</literal> 和
- <literal><replaceable>exp1</replaceable>!</literal> 相似,
- <literal>(<replaceable>exp1</replaceable>)?if_exists</literal> 和
- <literal>(<replaceable>exp1</replaceable>)!</literal> 相似。不同之处在于,
- 用 <literal>if_exists</literal> 的默认值不仅仅同时是空字符串,空序列和空哈希表,
- 而且布尔值 <literal>false</literal> 和不做任何事情的变换,还有忽略所有参数。</para>
- </listitem>
-
- <listitem>
- <para><indexterm>
- <primary>web_safe built-in</primary>
- </indexterm><literal>web_safe</literal>:和 <link
- linkend="ref_builtin_html"><literal>html</literal></link> 相同。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_depr_oldmacro">
- <title>老式的 macro 和 call 指令</title>
-
- <anchor xml:id="ref.directive.oldmacro"/>
-
- <anchor xml:id="ref.directive.call"/>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal><#macro <replaceable>name</replaceable>(<replaceable>argName1</replaceable>, <replaceable>argName2</replaceable>, <replaceable>... argNameN</replaceable>)>
- ...
-</#macro></literal>
-
-<literal><#call <replaceable>name</replaceable>(<replaceable>argValue1</replaceable>, <replaceable>argValue2</replaceable>, <replaceable>... argValueN</replaceable>)></literal></programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>name</replaceable></literal>:宏的名称(不是表达式)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>argName1</replaceable></literal>,
- <literal><replaceable>argName2</replaceable></literal>等:
- 存储参数值的 <link linkend="dgui_misc_var">局部变量</link> 的名称(不是表达式)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>argValue1</replaceable></literal>,
- <literal><replaceable>argValue2</replaceable></literal>,
- 等:表达式,参数的值</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <note>
- <para>这是 FreeMarker 2.1 版本的文档中宏还有宏它相关的指令。
- 这仍然可以用,但是已经被废弃了。你也许想阅读 FreeMarker2.2+ 版本的参考:
- <link linkend="ref.directive.macro">macro, return</link>,<link
- linkend="ref.directive.userDefined">自定义指令调用</link></para>
- </note>
-
- <para>宏是关联名称的模板段。你可以在你的模板中的很多位置使用命名的代码段,
- 所以它可以在重复的任务中帮助你。宏可以有参数,这会在你调用它的时候影响生成的输出。</para>
-
- <para>你可以使用 <literal>macro</literal> 指令来定义宏,之后你可以在整个模板中定义宏。
- <literal>macro</literal> 指令本身不往输出中写任何东西,它只是用来定义宏。
- 例如这会定义一个名为 <literal>warning</literal> 的宏:</para>
-
- <programlisting role="template"><emphasis><#macro warning(message)></emphasis>
- <div align=center>
- <table border=1 bgcolor=yellow width="80%"><tr><td align=center>
- <b>Warning!</b>
- <p>${message}
- </td></tr></table>
- </div>
-<emphasis></#macro></emphasis></programlisting>
-
- <para>当你使用 <literal>call</literal> 指令和宏名称时,
- 宏定义体(在宏开始标签和结束标签之间)会被处理。例如,
- 这会调用名为 <literal>warning</literal> 的宏:</para>
-
- <programlisting role="template"><#call warning("Unplug the machine before opening the cover!")></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <div align=center>
- <table border=1 bgcolor=yellow width="80%"><tr><td align=center>
- <b>Warning!</b>
- <p>Unplug the machine before opening the cover!
- </td></tr></table>
- </div>
- </programlisting>
-
- <para>给 <literal>call</literal> 指令传递的参数可以在宏定义体中作为 <link
- linkend="dgui_misc_var">局部变量</link>访问。</para>
-
- <para>当调用宏时,必须指定和宏定义时指定的个数相同的参数。
- 例如,如果这是宏的定义:</para>
-
- <programlisting role="template"><#macro test(a, b, c)>Nothing...</#macro></programlisting>
-
- <para>那么这些是合法的宏调用:</para>
-
- <programlisting role="template"><#call test(1, 2, 3)>
-<#call test("one", 2 + x, [1234, 2341, 3412, 4123])></programlisting>
-
- <para>如果宏没有参数,那么可以忽略圆括号:</para>
-
- <programlisting role="template"><#macro test>mooo</#macro>
-<#call test></programlisting>
-
- <para>当你定义宏时,那么它在模板中就是可用的,你也只能在模板中来定义宏。
- 但是你可能想在更多模板中使用相同的宏。这种情况下你可以在公共文件中存储你定义的宏,
- 之后在所有你需要这些宏的模板中包含那个文件。</para>
-
- <para>调用定义在模板下部的宏是不错的<phrase role="forProgrammers">
- (因为宏在解析时间定义,而不是执行时间)</phrase>。然而,
- 如果宏定义被插入到 <literal>include</literal> 指令中,
- 它们知道 FreeMarker 执行 <literal>include</literal> 指令时才会可用。</para>
-
- <para>你可以用 <literal>return</literal> 指令在
- <literal></#macro></literal> 标签之前留下宏定义体。</para>
- </section>
- </section>
-
- <section xml:id="ref_depr_transform">
- <title>转换指令</title>
-
- <anchor xml:id="ref.directive.transform"/>
-
- <indexterm>
- <primary>transform directive</primary>
- </indexterm>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate">
-<literal><transform <replaceable>transVar</replaceable>>
- <replaceable>...</replaceable>
-</transform></literal>
-or
-<literal><transform <replaceable>transVar</replaceable> <replaceable>name1</replaceable>=<replaceable>value1</replaceable> <replaceable>name2</replaceable>=<replaceable>value2</replaceable> <replaceable>...</replaceable> <replaceable>nameN</replaceable>=<replaceable>valueN</replaceable>>
- <replaceable>...</replaceable>
-</transform></literal>
-</programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>transVar</replaceable></literal>:
- 要来转换的表达式</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>name1</replaceable></literal>,
- <literal><replaceable>name2</replaceable></literal>,...
- <literal><replaceable>nameN</replaceable></literal>:
- 参数的名称。文字值,不是表达式</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>value1</replaceable></literal>,
- <literal><replaceable>value2</replaceable></literal>, ...
- <literal><replaceable>valueN</replaceable></literal>:
- 算作参数值的表达式</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <note>
- <para>这个指令仍然可用,但是已经被废弃了。你也许想阅读 <link
- linkend="ref.directive.userDefined">自定义指令调用</link> 来查看它的替代物。</para>
- </note>
-
- <para>捕捉生成在它体内(也就是开始标签和结束标签之间)的输出,
- 之后让给定的转换物在写入最终的输出之前改变。</para>
-
- <para>比如:</para>
-
- <programlisting role="template"><p>A very simple HTML file:
-<pre>
-<emphasis><transform html_escape></emphasis>
-<html>
- <body>
- <p>Hello word!
- </body>
-</html>
-<emphasis></transform></emphasis>
-</pre></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"><p>A very simple HTML file:
-<pre>
-&lt;html&gt;
- &lt;body&gt;
- &lt;p&gt;Hello word!
- &lt;/body&gt;
-&lt;/html&gt;
-</pre></programlisting>
-
- <para>一些转换可能需要参数。参数的名称和意义依赖于转换的问题。
- 比如这里我们给出一个名为"var"的参数:</para>
-
- <programlisting role="template"><#-- This transform stores the output in the variable x,
- rather than sending it to the output -->
-<transform capture_output<emphasis> var="x"</emphasis>>
-some test
-</transform></programlisting>
-
- <para>这是程序员在数据模型中放置必要转换的任务。
- 对于可访问转换的名称和用法请问程序员。<phrase
- role="forProgrammers">最初对在 <literal>freemarker.template.utility</literal>
- 包中的大多数转换来说有 <link
- linkend="pgui_config_sharedvariables">共享变量</link>。要获取更多信息,请参考:<xref
- linkend="pgui_config_sharedvariables"/></phrase></para>
- </section>
- </section>
-
- <section xml:id="ref_depr_oldsyntax">
- <title>老式 FTL 语法</title>
-
- <indexterm>
- <primary>strict syntax</primary>
- </indexterm>
-
- <indexterm>
- <primary>old FTL syntax</primary>
- </indexterm>
-
- <indexterm>
- <primary>new FTL syntax</primary>
- </indexterm>
-
- <para>在FTL标签中使用 <literal>#</literal> 形式的FTL语法已经是不要求
- (在 2.1 版本之前是不允许的)的了。比如,你可以这样写代码:</para>
-
- <programlisting role="template"><html>
-<head>
- <title>Welcome!</title>
-</head>
-<body>
- <h1>Welcome ${user}!</h1>
- <p>We have there animals:
- <ul>
- <emphasis><list animals as animal></emphasis>
- <li>${animal.name} for ${animal.price} Euros
- <emphasis></list></emphasis>
- </ul>
- <emphasis><include "common_footer.html"></emphasis>
-</body>
-</html></programlisting>
-
- <para>而没有 <literal>#</literal> 样式语法的代码对于HTML作者来说更加自然,
- 它有很多的缺点,所以最终我们决定废弃它。使用新式语法(又称为"严格的语法"),
- <literal>#</literal> 是严格要求的。也就是说,像
- <literal><include "common_footer.html"></literal>
- 这样的东西将会原样出现在输出中,因为它们不被认为是FTL标签。
- 注意用户自定义指令使用 <literal>@</literal> <emphasis>代替</emphasis>
- <literal>#</literal>。</para>
-
- <para>然而,为了给用户时间来准备这种改变,在 FreeMarker 2.1 和 2.2 版本中,
- <literal>#</literal> 的用法是可选的,除非程序员调用 <literal>Configuration</literal>
- 的 <literal>setStrictSyntaxMode(true)</literal> 在 FreeMarker 配置中开启严格语法模式。
- 事实上,我们把这个强烈建议给程序员。从后续释出版本开始,这个设置将会初始设置为
- <literal>true</literal>。而且,如果你在模板文件中想使用严格语法或老式语法,你可以用 <link
- linkend="ref.directive.ftl"><literal>ftl</literal> 指令</link>来指定。</para>
-
- <para>"严格语法"比遗留的FTL语法的好处是:</para>
-
- <itemizedlist>
- <listitem>
- <para>对于FTL来说,所有
- <literal><#<replaceable>...</replaceable>></literal> 和
- <literal></#<replaceable>...</replaceable>></literal> 都是保留的:</para>
-
- <itemizedlist>
- <listitem>
- <para>我们可以引入新的指令而不破坏向后兼容。</para>
- </listitem>
-
- <listitem>
- <para>我们可以检测你是否创留了一个打字错误,也就是
- <literal><#inculde
- <replaceable>...</replaceable>></literal> 被视为解析时的错误,
- 而不是被静默地视为简单文本。</para>
- </listitem>
-
- <listitem>
- <para>对于第三方工具来处理模板(比如高亮语法显示)来说是简单的,
- 特别是因为它们不需要知道新释出版本中被引入的新指令。</para>
- </listitem>
-
- <listitem>
- <para>模板更易于阅读,因为很容易辨认嵌入在HTML或其他标记中的
- <literal><#...></literal> 标签。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><literal><#</literal> 和 <literal></#</literal> 是合法的XML
- (除了在CDATA段中),而且其他大多数SGML应用中也是合法的,
- 所以它们不能妨碍用在静态文本部分(比如你在生成的XML中有
- <literal>include</literal> 元素)的标签。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section xml:id="ref_depr_numerical_interpolation">
- <title>#{...}: 数字插值</title>
-
- <indexterm>
- <primary>#{...}</primary>
- </indexterm>
-
- <para>已经被废弃了:使用 <link
- linkend="ref.setting.number_format"><literal>number_format</literal>
- 设置项</link> 和 <link linkend="ref_builtin_string_for_number">the
- <literal>string</literal> 内建函数</link> 来代替。对于计算机使用
- (也就非本地的格式化)的格式化,使用 <link
- linkend="ref_builtin_c"><literal>c</literal> 内建函数</link>
- (比如 <literal><replaceable>number</replaceable>?c</literal>)。</para>
-
- <section>
- <title>概要</title>
-
- <programlisting role="metaTemplate"><literal>#{<replaceable>expression</replaceable>}</literal>
-或
-<literal>#{<replaceable>expression</replaceable>; <replaceable>format</replaceable>}</literal></programlisting>
-
- <para>这里:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para><literal><replaceable>expression</replaceable></literal>:
- 可以算作是数字的表达式</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>format</replaceable></literal>:
- 可选的格式说明符</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>描述</title>
-
- <para>数字插值被用来输出数值。如果表达式不能算成数字,
- 那么计算过程就会以抛出错误而终止。</para>
-
- <para>可选的格式说明符指定了使用
- <literal>m<replaceable>min</replaceable>M<replaceable>max</replaceable></literal>
- 语法显示的小数位数的最小和最大值。比如,<literal>m2M5</literal>
- 表示"最少两位,最多5位小数位"。最小值和最大值说明符部分可以被忽略。
- 如果仅指定最小值,那么最大值和最小值相等。如果仅指定最大值,那么最小值是0。</para>
-
- <para>输出的小数点字符是国际化的(根据当前本地设置),这表示它可能不是一个点。</para>
-
- <para>不像 <literal>${...}</literal>, <literal>#{...}</literal> 忽略 <link
- linkend="ref.setting.number_format"><literal>number_format</literal>
- 设置项</link>。实际上这是向后兼容的一个怪点,但是当你在如
- <literal><a href="quertyDatabase?id=#{id}"></literal>
- 这些情况打印数字时它可能是有用的,这里你肯定不想分组分隔符或像那些幻想的东西。
- 然而,从 FreeMarker 2.3.3 版本开始,而使用 <link
- linkend="ref_builtin_c"><literal>?c</literal> 内建函数</link> 来达到这个目的,
- 比如 <literal><a href="quertyDatabase?id=${id?c}"></literal>。</para>
-
- <para>比如。假设 <literal>x</literal> 是
- <literal>2.582</literal> 而 <literal>y</literal> 是
- <literal>4</literal>:</para>
-
- <programlisting role="template"> <#-- If the language is US English the output is: -->
-#{x} <#-- 2.582 -->
-#{y} <#-- 4 -->
-#{x; M2} <#-- 2.58 -->
-#{y; M2} <#-- 4 -->
-#{x; m1} <#-- 2.6 -->
-#{y; m1} <#-- 4.0 -->
-#{x; m1M2} <#-- 2.58 -->
-#{y; m1M2} <#-- 4.0 --></programlisting>
- </section>
- </section>
- </chapter>
- </part>
-
- <part xml:id="xgui">
- <title>XML处理指南</title>
-
- <preface xml:id="xgui_preface">
- <title>前言</title>
-
- <para>尽管 FreeMarker 最初被设计用作Web页面的模板引擎,
- 对于2.3版本来说,它的另外一个应用领域目标是:
- 转换XML到任意的文本输出(比如HTML)。
- 因此,在很多情况下,FreeMarker 也是一个可选的XSLT。</para>
-
- <para>从技术上来说,在转换XML文档上没有什么特别之处。
- 它和你使用 FreeMarker 做其他事情都是一样的:
- 你将XML文档丢到数据模型中(和其他可能的变量),
- 然后你将FTL模板和数据模型合并来生成输出文本。
- 对于更好的XML处理的额外特性是结点FTL变量类型
- (在通用的树形结构中象征一个结点,不仅仅是对XML有用)和用内建函数,
- 指令处理它们,你使用的XML包装器会暴露XML文档,并将作为模板的FTL变量。</para>
-
- <para>使用 FreeMarker 和XSLT有什么不同?FTL语言有常规的命令式/过程式的逻辑。
- 另一方面,XSLT是声明式的语言,由"很聪明"的人设计出来,
- 所以它并不能轻易吸收它的逻辑,也不会在很多情况下使用。而且它的语法也非常繁琐。
- 然而,当你处理XML文档时,XSLT的"应用模板"方法可以非常方便,
- 因此 FreeMarker 支持称作"访问者模式"的相似事情。
- 所以在很多应用程序中,写FTL的样式表要比写XSLT的样式表容易很多。
- 另外一个根本的不同是FTL转换结点树到文本,而XSLT转换一课树到另一棵树。
- 所以你不能经常在使用XSLT的地方使用 FreeMarker。</para>
- </preface>
-
- <chapter xml:id="xgui_expose">
- <title>揭示XML文档</title>
-
- <indexterm>
- <primary>XML</primary>
-
- <secondary>exposing</secondary>
- </indexterm>
-
- <section xml:id="xgui_expose_dom">
- <title>结点树</title>
-
- <para>我们使用如下示例的XML文档:</para>
-
- <anchor xml:id="misc.example.bookXml"/>
-
- <programlisting role="unspecified"><book>
- <title>Test Book</title>
- <chapter>
- <title>Ch1</title>
- <para>p1.1</para>
- <para>p1.2</para>
- <para>p1.3</para>
- </chapter>
- <chapter>
- <title>Ch2</title>
- <para>p2.1</para>
- <para>p2.2</para>
- </chapter>
-</book></programlisting>
-
- <para>W3C 的 DOM 定义XML文档模型为结点树。上面XML的结点树可以被视为:</para>
-
- <programlisting role="unspecified">document
- |
- +- element book
- |
- +- text "\n "
- |
- +- element title
- | |
- | +- text "Test Book"
- |
- +- text "\n "
- |
- +- element chapter
- | |
- | +- text "\n "
- | |
- | +- element title
- | | |
- | | +- text "Ch1"
- | |
- | +- text "\n "
- | |
- | +- element para
- | | |
- | | +- text "p1.1"
- | |
- | +- text "\n "
- | |
- | +- element para
- | | |
- | | +- text "p1.2"
- | |
- | +- text "\n "
- | |
- | +- element para
- | |
- | +- text "p1.3"
- |
- +- element
- |
- +- text "\n "
- |
- +- element title
- | |
- | +- text "Ch2"
- |
- +- text "\n "
- |
- +- element para
- | |
- | +- text "p2.1"
- |
- +- text "\n "
- |
- +- element para
- |
- +- text "p2.2"</programlisting>
-
- <para>请注意,烦扰的 <literal>"\n "</literal> 是行的中断
- (这里用 <literal>\n</literal> 指示,在FTL字符串中使用转义序列)
- 和标记直接的缩进空格。</para>
-
- <para>请注意和DOM相关的术语:</para>
-
- <itemizedlist>
- <listitem>
- <para>一棵树最上面的结点称为根 <emphasis
- role="term">root</emphasis>,在XML文档中,它通常是"文档"结点,
- 而不是最顶层元素(本例中的 <literal>book</literal>)。</para>
- </listitem>
-
- <listitem>
- <para>如果B是A的 <emphasis>直接</emphasis> 后继,
- 我们说B结点是A结点的子结点 <emphasis role="term">child</emphasis>。比如,
- 两个 <literal>chapter</literal> 元素是 <literal>book</literal> 元素的子结点,
- 但是 <literal>para</literal> 元素就不是。</para>
- </listitem>
-
- <listitem>
- <para>如果A是B的 <emphasis>直接</emphasis> 前驱,也就是说,
- 如果B是A的子结点,我们说结点A是结点B的父结点 <emphasis
- role="term">parent</emphasis>。比如,<literal>book</literal>
- 元素是两个 <literal>chapter</literal> 元素的父结点,
- 但是它不是 <literal>para</literal> 元素的父结点。</para>
- </listitem>
-
- <listitem>
- <para>XML文档中可以出现几种成分,比如元素,文本,注释,处理指令等。
- 所有这些成分都是DOM树的结点,所以就有元素结点,文本结点,注释结点等。
- 原则上,元素的属性也是树的结点--它们是元素的子结点--,
- 但是,通常我们(还有其他XML相关的技术)不包含元素的子结点。
- 所以基本上它们不被记为子结点。</para>
- </listitem>
- </itemizedlist>
-
- <para>程序员将DOM树的文档结点放到 FreeMarker 的数据模型中,
- 那么模板开发人员可以以变量为出发点来使用DOM树。</para>
-
- <para>FTL中的DOM结点和 <emphasis role="term">node variables</emphasis>
- 结点变量对应。这是变量类型,和字符串,数字,哈希表等类型相似。
- 结点变量类型使得 FreeMarker 来获取一个结点的父结点和子结点成为可能。
- 这是技术上需要允许模板开发人员在结点间操作,也就是,使用<link
- linkend="ref_builtins_node">node 内建函数</link> 或者 <link
- linkend="ref.directive.visit"><literal>visit</literal></link> 和
- <link
- linkend="ref.directive.recurse"><literal>recurse</literal></link>
- 指令;我们会在后续章节中展示它们的使用。</para>
- </section>
-
- <section xml:id="xgui_expose_put">
- <title>将XML放到数据模型中</title>
-
- <note>
- <para>本部分是对程序员来说的。</para>
- </note>
-
- <para>创建一个简单的程序来运行下面的示例是非常容易的。
- 仅仅用下面这个例子来替换<link
- linkend="pgui_quickstart_all">程序开发指南中快速入门示例</link>
- 中的"Create a data-model"部分:</para>
-
- <programlisting role="unspecified">/* Create a data-model */
-Map root = new HashMap();
-root.put(
- "doc",
- freemarker.ext.dom.NodeModel.parse(new File("<replaceable>the/path/of/the.xml</replaceable>")));</programlisting>
-
- <para>然后你可以在基本的输出(通常是终端屏幕)
- 中得到一个程序可以输出XML转换的结果。</para>
-
- <para>注意:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>parse</literal> 方法默认移除注释和处理指令结点。
- 参见API文档获取详细信息。</para>
- </listitem>
-
- <listitem>
- <para><literal>NodeModel</literal> 也允许你直接包装
- <literal>org.w3c.dom.Node</literal>。
- 首先你也许想用静态的实用方法清空DOM树,比如
- <literal>NodeModel.simplify</literal> 或你自定义的清空规则。</para>
- </listitem>
- </itemizedlist>
-
- <para>请注意,有可用的工具,你可以使用它们从XML文档中来生成文件,
- 所以你不需对通用任务来写自己的代码。<link
- linkend="pgui_misc_ant">请参考这里...</link></para>
- </section>
- </chapter>
-
- <chapter xml:id="xgui_imperative">
- <title>必要的XML处理</title>
-
- <indexterm>
- <primary>XML</primary>
-
- <secondary>imperative processing</secondary>
- </indexterm>
-
- <section xml:id="xgui_imperative_learn">
- <title>基本内容</title>
-
- <note>
- <para>这部分我们使用的DOM树和变量都是<link
- linkend="xgui_expose">前一章</link>做的那个。</para>
- </note>
-
- <para>假设程序员在数据模型中放置了一个XML文档,就是名为
- <literal>doc</literal> 的变量。这个变量和<link
- linkend="xgui_expose_dom">DOM 树</link>的根结点"document"对应。
- 真实的变量 <literal>doc</literal> 之后结构是非常复杂的,
- 大约类似DOM树。所以为了避免钻牛角尖,我们通过例子来看看如何使用。</para>
-
- <section>
- <title>通过名称来访问元素</title>
-
- <para>这个FTL打印book的title:</para>
-
- <programlisting role="template"><h1>${doc.book.title}</h1></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"><h1>Test Book</h1></programlisting>
-
- <para>正如你所看到的,<literal>doc</literal> 和 <literal>book</literal>
- 都可以当作哈希表来使用。你可以按照子变量的形式来获得它们的子结点。
- 基本上,你用描述路径的方法来访问在DOM树中的目标(元素<literal>title</literal>)。
- 你也许注意到了上面有一些是假象:使用 <literal>${doc.book.title}</literal>,
- 就好像我们指示 FreeMarker 打印 <literal>title</literal> 元素本身,
- 但是我们应该打印它的子元素文本(看看 <link linkend="xgui_expose_dom">DOM 树</link>)。
- 那也可以办到,因为元素不仅仅是哈希表变量,也是字符串变量。
- 元素结点的标量是从它的文本子结点级联中获取的字符串结果。然而,如果元素有子元素,
- 尝试使用一个元素作为标量会引起错误。比如<literal>${doc.book}</literal>将会以错误而终止。</para>
-
- <para>该FTL打印2个chapter的title:</para>
-
- <programlisting role="template"><h2>${doc.book.chapter[0].title}</h2>
-<h2>${doc.book.chapter[1].title}</h2></programlisting>
-
- <para>这里,<literal>book</literal> 有两个 <literal>chapter</literal> 子元素,
- <literal>doc.book.chapter</literal> 是存储两个元素结点的序列。
- 因此,我们可以概括上面的FTL,所以它以任意chapter的数量起作用:</para>
-
- <programlisting role="template"><#list doc.book.chapter as ch>
- <h2>${ch.title}</h2>
-</#list></programlisting>
-
- <para>但是如果只有一个chapter会怎么样呢?实际上,
- 当你访问一个作为哈希表子变量的元素时,<emphasis>通常</emphasis>
- 也可以是序列(不仅仅是哈希表和字符串),但如果序列只包含一个项,
- 那么变量也作为项目自身。所以,回到第一个示例中,它也会打印book的title:</para>
-
- <programlisting role="template"><h1>${doc.book[0].title[0]}</h1></programlisting>
-
- <para>但是你知道那里就只有一个 <literal>book</literal> 元素,
- 而且book也就只有一个title,所以你可以忽略那些 <literal>[0]</literal>。
- 如果book恰好有一个 <literal>chapter</literal>(否则它就是模糊的:
- 它怎么知道你想要的是哪个 <literal>chapter</literal> 的 <literal>title</literal>?
- 所以它就会以错误而停止),<literal>${doc.book.chapter.title}</literal> 也可以正常进行。
- 但是因为一个book可以有很多chapter,你不能使用这种形式。如果元素
- <literal>book</literal> 没有子元素 <literal>chapter</literal>,
- 那么 <literal>doc.book.chapter</literal> 将是一个长度为零的序列,
- 所以用FTL <literal><#list ...></literal> 也可以进行。</para>
-
- <para>知道这样一个结果是很重要的,比如,如果 <literal>book</literal>
- 没有 <literal>chapter</literal>,那么 <literal>book.chapter</literal>
- 就是一个空序列,所以 <literal>doc.book.chapter??</literal>
- 就 <emphasis>不会</emphasis> 是 <literal>false</literal>,它就一直是
- <literal>true</literal>!类似地,<literal>doc.book.somethingTotallyNonsense??</literal>
- 也不会是 <literal>false</literal>。来检查是否有子结点,可以使用
- <literal>doc.book.chapter[0]??</literal>(或<literal>doc.book.chapter?size == 0</literal>)。
- 当然你可以使用类似所有的<link
- linkend="dgui_template_exp_missing">空值处理操作符</link>
- (比如 <literal>doc.book.author[0]!"Anonymous"</literal> ),只是不要忘了那个
- <literal>[0]</literal>。</para>
-
- <note>
- <para>序列的大小是1的规则是方便XML包装的方便特性(通过多类型的FTL变量来实现)。
- 通常其他序列将不会起作用。</para>
- </note>
-
- <para>现在我们完成了打印每个chapter所有的 <literal>para</literal> 示例:</para>
-
- <programlisting role="template"><h1>${doc.book.title}</h1>
-<#list doc.book.chapter as ch>
- <h2>${ch.title}</h2>
- <#list ch.para as p>
- <p>${p}
- </#list>
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"><h1>Test</h1>
- <h2>Ch1</h2>
- <p>p1.1
- <p>p1.2
- <p>p1.3
- <h2>Ch2</h2>
- <p>p2.1
- <p>p2.2</programlisting>
-
- <para>上面的FTL可以书写的更加漂亮:</para>
-
- <programlisting role="template"><#assign book = doc.book>
-<h1>${book.title}</h1>
-<#list book.chapter as ch>
- <h2>${ch.title}</h2>
- <#list ch.para as p>
- <p>${p}
- </#list>
-</#list></programlisting>
-
- <para>最终,一个"广义的"子结点选择机制是:
- 模板列出所有示例XML文档的<literal>para</literal>:</para>
-
- <programlisting role="template"><#list doc.book.chapter.para as p>
- <p>${p}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>p1.1
- <p>p1.2
- <p>p1.3
- <p>p2.1
- <p>p2.2
- </programlisting>
-
- <para>这个示例说明了哈希表变量选择序列子结点的做法
- (在前面示例中的序列大小为1)。在这个具体的例子中,子变量
- <literal>chapter</literal> 返回一个大小为2的序列
- (因为有两个 <literal>chapter</literal>),之后子变量
- <literal>para</literal> 在那个序列中选择 <literal>para</literal>
- 所有结点的子结点。</para>
-
- <para>这种机制的一个负面结果是类似于
- <literal>doc.somethingNonsense.otherNonsesne.totalNonsense</literal>
- 这样的东西会被算作是空序列,而且你也不会得到任何错误信息。</para>
- </section>
-
- <section>
- <title>访问属性</title>
-
- <para>这个XML和原来的那个是相同的,除了它使用title属性,而不是元素:</para>
-
- <programlisting role="unspecified"><!-- THIS XML IS USED FOR THE "Accessing attributes" CHAPTER ONLY! -->
-<!-- Outside this chapter examples use the XML from earlier. -->
-
-<book title="Test">
- <chapter title="Ch1">
- <para>p1.1</para>
- <para>p1.2</para>
- <para>p1.3</para>
- </chapter>
- <chapter title="Ch2">
- <para>p2.1</para>
- <para>p2.2</para>
- </chapter>
-</book></programlisting>
-
- <para>一个元素的属性可以通过和元素的子元素一样的方式来访问,
- 除了你在属性名的前面放置一个@符号:</para>
-
- <programlisting role="template"><#assign book = doc.book>
-<h1>${book.@title}</h1>
-<#list book.chapter as ch>
- <h2>${ch.@title}</h2>
- <#list ch.para as p>
- <p>${p}
- </#list>
-</#list></programlisting>
-
- <para>这会打印出和前面示例相同的结果。</para>
-
- <para>按照和获取子结点一样的逻辑来获得属性,所以上面的
- <literal>ch.@title</literal> 结果就是大小为1的序列。如果没有
- <literal>title</literal> 属性,那么结果就是一个大小为0的序列。
- 所以要注意,这里使用内建函数也是有问题的:如果你很好奇
- <literal>foo</literal> 是否含有属性 <literal>bar</literal>,
- 那么你不得不写 <literal>foo.@bar[0]??</literal>。
- (<literal>foo.@bar??</literal> 是不对的,因为它总是返回
- <literal>true</literal>)。类似地,如果你想要一个
- <literal>bar</literal> 属性的默认值,那么你就不得不写
- <literal>foo.@bar[0]!"theDefaultValue"</literal>。</para>
-
- <para>正如子元素那样,你可以选择多结点的属性。
- 例如,这个模板将打印所有chapter的title属性:</para>
-
- <programlisting role="template"><#list doc.book.chapter.@title as t>
- ${t}
-</#list></programlisting>
- </section>
-
- <section>
- <title>探索DOM树</title>
-
- <para>这个FTL将会枚举所有book元素的子结点:</para>
-
- <programlisting role="template"><#list doc.book?children as c>
-- ${c?node_type} <#if c?node_type == 'element'>${c?node_name}</#if>
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- text
-- element title
-- text
-- element chapter
-- text
-- element chapter
-- text</programlisting>
-
- <para><literal>?node_type</literal> 的意思可能没有解释清楚。
- 有一些在DOM树中存在的结点类型,比如 <literal>"element"</literal>,
- <literal>"text"</literal>,<literal>"comment"</literal>,
- <literal>"pi"</literal>等。</para>
-
- <para><literal>?node_name</literal> 返回结点的结点名称。
- 对于其他的结点类型,也会返回一些东西,但是它对声明的XML处理更有用,这会在<link
- linkend="xgui_declarative">后面章节</link>中讨论。</para>
-
- <para>如果book元素有属性,由于实际的原因它可能
- <emphasis>不会</emphasis> 在上面的列表中出现。
- 但是你可以获得包含元素所有属性的列表,使用变量元素的子变量
- <literal>@@</literal>。如果你将XML的第一行修改为这样:</para>
-
- <programlisting role="unspecified"><book foo="Foo" bar="Bar" baaz="Baaz"></programlisting>
-
- <para>然后运行这个FTL:</para>
-
- <programlisting role="template"><#list doc.book.@@ as attr>
-- ${attr?node_name} = ${attr}
-</#list></programlisting>
-
- <para>然后得到这个输出(或者其他相似的结果):</para>
-
- <programlisting role="output">- baaz = Baaz
-- bar = Bar
-- foo = Foo</programlisting>
-
- <para>要返回子结点的列表,有一个方便的子变量来仅仅列出元素的子元素:</para>
-
- <programlisting role="template"><#list doc.book.* as c>
-- ${c?node_name}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">- title
-- chapter
-- chapter</programlisting>
-
- <para>你可以使用内建函数 <literal>parent</literal> 来获得元素的父结点:</para>
-
- <programlisting role="template"><#assign e = doc.book.chapter[0].para[0]>
-<#-- Now e is the first para of the first chapter -->
-${e?node_name}
-${e?parent?node_name}
-${e?parent?parent?node_name}
-${e?parent?parent?parent?node_name}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">para
-chapter
-book
-@document</programlisting>
-
- <para>在最后一行你访问到了DOM树的根结点,文档结点。
- 它不是一个元素,这就是为什么得到了一个奇怪的名字;
- 现在我们不来处理它。很明显,文档结点没有父结点。</para>
-
- <para>你可以使用内建函数 <literal>root</literal> 来快速返回到文档结点:</para>
-
- <programlisting role="template"><#assign e = doc.book.chapter[0].para[0]>
-${e?root?node_name}
-${e?root.book.title}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">@document
-Test Book</programlisting>
-
- <para>在内建函数完整的列表中你可以用来在DOM树中导航,
- 可以阅读<link linkend="ref_builtins_node">结点内建函数参考</link>。</para>
- </section>
-
- <section>
- <title>使用XPath表达式</title>
-
- <note>
- <para>XPath表达式仅在<link
- xlink:href="http://jaxen.org/">Jaxen</link>(推荐使用,
- 但是使用至少Jaxen 1.1-beta-8版本,不能再老了)或<link
- xlink:href="http://xml.apache.org/xalan/">Apache Xalan</link>库可用时有效。
- (Apache Xalan库在Sun J2SE 1.4,1.5和1.6(也许在后续版本)中已经包含了;
- 不需要独立的Xalan的jar包。)</para>
- </note>
-
- <note>
- <para>不要使用前一部分的示例XML,那里的<literal>title</literal>
- 是一个属性;仅对那部分使用。</para>
- </note>
-
- <para>如果哈希表的键使用了结点变量而不能被解释(<link
- linkend="xgui_imperative_formal">下一部分</link>对此精确定义),
- 那么它就会被当作Xpath表达式被解释。要获得XPath的更多信息,
- 可以访问<link
- xlink:href="http://www.w3.org/TR/xpath">http://www.w3.org/TR/xpath</link>。</para>
-
- <para>例如,这里我们列出 <literal>title</literal>
- 元素(不是属性!)内容为"Ch1"的chapter的 <literal>para</literal> 元素:</para>
-
- <programlisting role="template"><#list doc["book/chapter[title='Ch1']/para"] as p>
- <p>${p}
-</#list></programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output"> <p>p1.1
- <p>p1.2
- <p>p1.3</programlisting>
-
- <para>长度为1(在前面部分解释过了)的序列的规则也代表XPath的结果。
- 也就是说,如果结果序列仅仅包含1个结点,它也会当作结点自身。
- 例如,打印chapter元素"Ch1"第一段:</para>
-
- <programlisting role="template">${doc["book/chapter[title='Ch1']/para[1]"]}</programlisting>
-
- <para>这也会输出相同内容:</para>
-
- <programlisting role="template">${doc["book/chapter[title='Ch1']/para[1]"][0]}</programlisting>
-
- <para>XPath表达式的内容结点(或者是结点序列)
- 是哈希表子变量被用作发布XPath表达式的结点。
- 因此,这将打印和上面例子相同的内容:</para>
-
- <programlisting role="template">${doc.book["chapter[title='Ch1']/para[1]"]}</programlisting>
-
- <para>请注意,现在你可以使用0序列或多(比1多)结点作为内容,
- 这只在程序员已经建立 FreeMarker 使用Jaxen而不是Xalan时才可以。</para>
-
- <para>也要注意XPath序列的项索引从1开始,而FTL的序列项索引是用0开始的。
- 因此,要选择第一个chapter,XPath表达式是 <literal>"/book/chapter[1]"</literal>,
- 而FTL表达式是 <literal>book.chapter[0]</literal>。</para>
-
- <para>如果程序员设置 FreeMarker 使用Jaxen而不是Xalan,
- 那么 FreeMarker 的变量在使用XPath变量引用时是可见的:</para>
-
- <programlisting role="template"><#assign <emphasis>currentTitle</emphasis> = "Ch1">
-<#list doc["book/chapter[title=<emphasis>$currentTitle</emphasis>]/para"] as p>
-<replaceable>...</replaceable></programlisting>
-
- <para>请注意,<literal>$currentTitle</literal> 不是 FreeMarker 的插值,
- 因为那里没有 <literal>{</literal> 和 <literal>}</literal>。
- 那是XPath表达式。</para>
-
- <para>一些XPath表达式的结果不是结点集,而是字符串,数字或者布尔值。
- 对于那些XPath表达式,结果分别是FTL字符串,数字或布尔值变量。
- 例如,下面的例子将会计算XML文档中 <literal>para</literal> 元素的总数,
- 所以结果是一个数字:</para>
-
- <programlisting role="template">${x["count(//para)"]}</programlisting>
-
- <para>将会输出:</para>
-
- <programlisting role="output">5</programlisting>
- </section>
-
- <section>
- <title>XML命名空间</title>
-
- <indexterm>
- <primary>XML namespace</primary>
-
- <secondary>in imperative processing</secondary>
- </indexterm>
-
- <para>默认来说,当你编写如 <literal>doc.book</literal> 这样的东西时,
- 那么它会选择属于任何XML命名空间(和XPath相似)名字为
- <literal>book</literal> 的元素。如果你想在XML命名空间中选择一个元素,
- 你必须注册一个前缀,然后使用它。比如,如果元素 <literal>book</literal>
- 是命名空间 <literal>http://example.com/ebook</literal>,
- 那么你不得不关联一个前缀,要在模板的顶部使用 <link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- 指令</link> 的 <literal>ns_prefixes</literal> 参数:</para>
-
- <programlisting role="template"><#ftl ns_prefixes={"e":"http://example.com/ebook"}>
-</programlisting>
-
- <para>现在你可以编写如 <literal>doc["e:book"]</literal> 的表达式。
- (因为冒号会混淆 FreeMarker,方括号语法的使用是需要的。)</para>
-
- <para><literal>ns_prefixes</literal> 的值作为哈希表,
- 你可以注册多个前缀:</para>
-
- <programlisting role="template"><#ftl ns_prefixes={
- "e":"http://example.com/ebook",
- "f":"http://example.com/form",
- "vg":"http://example.com/vectorGraphics"}
-></programlisting>
-
- <para><literal>ns_prefixes</literal> 参数影响整个 <link
- linkend="dgui_misc_namespace">FTL 命名空间</link>。
- 这就意味着实际中,你在主页面模板中注册的前缀必须在所有的 <literal><#include
- ...></literal> 的模板中可见,而不是 <literal><#imported
- ...></literal> 的模板(经常用来引用FTL库)。从另外一种观点来说,
- 一个FTL库可以注册XML命名空间前缀来为自己使用,而前缀注册不会干扰主模板和其他库。</para>
-
- <para>要注意,如果一个输入模板是给定XML命名空间域中的,
- 为了方便你可以设置它为默认命名空间。这就意味着如果你不使用前缀,
- 如在 <literal>doc.book</literal> 中,那么它会选择属于默认命名空间的元素。
- 这个默认命名空间的设置使用保留前缀 <literal>D</literal>,例如:</para>
-
- <programlisting role="template"><#ftl ns_prefixes={"D":"http://example.com/ebook"}>
-</programlisting>
-
- <para>现在表达式 <literal>doc.book</literal> 选择属于XML命名空间
- <literal>http://example.com/ebook</literal> 的 <literal>book</literal> 元素。
- 不幸的是,XPath不支持默认命名空间。因此,在XPath表达式中,
- 没有前缀的元素名称通常选择不输入任何XML命名空间的元素。然而,
- 在默认命名空间中访问元素你可以直接使用前缀<literal>D</literal>,比如:
- <literal>doc["D:book/D:chapter[title='Ch1']"]</literal>。</para>
-
- <para>请注意,当你使用默认命名空间时,那么你可以使用保留前缀
- <literal>N</literal> 来选择不属于任意结点空间的元素。比如
- <literal>doc.book["N:foo"]</literal>。这对XPath表达式不起作用,
- 上述的都可以写作 <literal>doc["D:book/foo"]</literal>。</para>
- </section>
-
- <section>
- <title>不要忘了转义!</title>
-
- <para>我们在所有的示例中都犯了大错。我们生成HTML格式的输出,
- HTML格式保留如 <literal><</literal>,<literal>&</literal>
- 等的字符。所以当我们打印普通文本(比如标题和段落)时,
- 我们不得不转义它,因此,示例的正确版本是:</para>
-
- <programlisting role="template"><emphasis><#escape x as x?html></emphasis>
-<#assign book = doc.book>
-<h1>${book.title}</h1>
-<#list book.chapter as ch>
- <h2>${ch.title}</h2>
- <#list ch.para as p>
- <p>${p}
- </#list>
-</#list>
-<emphasis></#escape></emphasis></programlisting>
-
- <para>所以如果book的标题是"Romeo & Julia",
- 那么HTML输出的结果就是正确的:</para>
-
- <programlisting role="output"><replaceable>...</replaceable>
-<h1>Romeo &amp; Julia</h1>
-<replaceable>...</replaceable></programlisting>
- </section>
- </section>
-
- <section xml:id="xgui_imperative_formal">
- <title>具体细节</title>
-
- <para>每一个和DOM树中单独结点对应的变量都是结点和哈希表类型的多类型的变量
- (对于程序员:实现<literal>TemplateNodeModel</literal>
- 和 <literal>TemplateHashModel</literal> 两个接口)。因此,你可以使用<link
- linkend="ref_builtins_node">结点内建函数</link>来处理它们。
- 哈希表的键被解释为XPath表达式,除了在下面表格中显示的特殊键。
- 一些结点变量也有字符串类型,所以你可以使用它们作为字符串变量
- (对于程序员:他们需要实现 <literal>TemplateScalarModel</literal> 接口)。</para>
-
- <anchor xml:id="misc.xguiTable"/>
-
- <informaltable border="1">
- <thead>
- <tr>
- <th>结点类型 (<literal>?node_type</literal>)</th>
-
- <th>结点名称 (<literal>?node_name</literal>)</th>
-
- <th>字符串值 (比如 <literal><p>${node}</literal>)</th>
-
- <th>特殊哈希表键</th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td><literal>"document"</literal></td>
-
- <td><literal>"@document"</literal></td>
-
- <td>没有字符串值。(当你尝试用字符串来使用时发生错误。)</td>
-
- <td><literal>"<replaceable>elementName</replaceable>"</literal>,
- <literal>"<replaceable>prefix</replaceable>:<replaceable>elementName</replaceable>"</literal>,
- <literal>"*"</literal>, <literal>"**"</literal>,
- <literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"element"</literal></td>
-
- <td><literal>"<replaceable>name</replaceable>"</literal>:
- 元素的名称。这是本地命名(也就是没有命名空间前缀的名字)。</td>
-
- <td>如果它没有子元素,所有子结点的文本串联起来。
- 当你尝试用它当字符串时会发生错误。</td>
-
- <td><literal>"<replaceable>elementName</replaceable>"</literal>,
- <literal>"<replaceable>prefix</replaceable>:<replaceable>elementName</replaceable>"</literal>,
- <literal>"*"</literal>, <literal>"**"</literal>,
- <literal>"@<replaceable>attrName</replaceable>"</literal>,
- <literal>"@<replaceable>prefix</replaceable>:<replaceable>attrName</replaceable>"</literal>,
- <literal>"@@"</literal>, <literal>"@*"</literal>,
- <literal>"@@start_tag"</literal>,
- <literal>"@@end_tag"</literal>,
- <literal>"@@attributes_markup"</literal>,
- <literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal>, <literal>"@@qname"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"text"</literal></td>
-
- <td><literal>"@text"</literal></td>
-
- <td>文本本身。</td>
-
- <td><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"pi"</literal></td>
-
- <td><literal>"@pi$<replaceable>target</replaceable>"</literal></td>
-
- <td>在目标名称和 <literal>?></literal> 之间的部分。</td>
-
- <td><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"comment"</literal></td>
-
- <td><literal>"@comment"</literal></td>
-
- <td>注释的文本,不包括分隔符 <literal><!--</literal>
- 和 <literal>--></literal>。</td>
-
- <td><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"attribute"</literal></td>
-
- <td><literal>"<replaceable>name</replaceable>"</literal>:
- 属性的名字。这是本地命名(也就是没有命名空间前缀的名字)。</td>
-
- <td>属性的值。</td>
-
- <td><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal>, <literal>"@@qname"</literal></td>
- </tr>
-
- <tr>
- <td><literal>"document_type"</literal></td>
-
- <td><literal>"@document_type$<replaceable>name</replaceable>"</literal>:
- <literal><replaceable>name</replaceable></literal> 是文档元素的名字。</td>
-
- <td>没有字符串值。(当你尝试用字符串来使用时发生错误。)</td>
-
- <td><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal>,
- <literal>"@@text"</literal></td>
- </tr>
- </tbody>
- </informaltable>
-
- <para>注意:</para>
-
- <itemizedlist>
- <listitem>
- <para>没有CDATA类型,CDATA结点被透明地认为是文本结点。</para>
- </listitem>
-
- <listitem>
- <para>变量 <emphasis>不</emphasis> 支持 <literal>?keys</literal>
- 和 <literal>?values</literal>。</para>
- </listitem>
-
- <listitem>
- <para>元素和属性结点的名字是本地命名,也就是,它们不包含命名空间前缀。
- 结点所属的命名空间的URI可以使用内建函数 <literal>?node_namespace</literal>
- 来获得。</para>
- </listitem>
-
- <listitem>
- <para>XPath表达式需要Jaxen(推荐使用,但是请使用1.1-beta-8之后的版本;
- <link xlink:href="http://jaxen.org/">可以从此处下载</link>)
- 或者Apache的Xalan库,否则会有错误并且终止模板的执行。要注意,
- 隐藏在XPath表达式中一些特殊哈希表键有相同的意思,
- 尽管没有XPath可用的实现,但那些XPath表达式仍然会起作用。
- <phrase role="forProgrammers">如果Xalan和Jaxen两者都可用,
- FreeMarker 将会使用Xalen,除非你在Java代码中通过调用
- <literal>freemarker.ext.dom.NodeModel.useJaxenXPathSupport()</literal>
- 方法,才会使用Jaxen。</phrase></para>
- </listitem>
-
- <listitem>
- <para>如果Jaxen被用来支持XPath(而不是Xalan),
- 那么 FreeMarker 变量通过XPath变量的引用是可见的
- (比如<literal>doc["book/chapter[title=$currentTitle]"]</literal>)。</para>
- </listitem>
- </itemizedlist>
-
- <para>特殊哈希表键的含义:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>"<replaceable>elementName</replaceable>"</literal>,
- <literal>"<replaceable>prefix</replaceable>:<replaceable>elementName</replaceable>"</literal>:
- 返回元素名为 <literal><replaceable>elementName</replaceable></literal>
- 的子结点的序列。(注意这里的"子"结点就是 <emphasis>直接</emphasis> 后继)
- 选择是XML命名空间的标识,除非XML文档用不再命名空间标识结点中的XML解析器解析。
- 在XML命名空间标识结点中,不含前缀的名字(<replaceable>elementName</replaceable>)
- 仅仅选择不属于任何XML命名空间的元素(除非你已经注册了默认的XML命名空间),
- 有前缀的名字(<replaceable>prefix</replaceable>:<replaceable>elementName</replaceable>)
- 选择属于前缀代表命名空间的元素。前缀的注册和默认XML命名空间的设置是由<link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- 指令</link> 的 <literal>ns_prefixes</literal> 参数完成的。</para>
- </listitem>
-
- <listitem>
- <para><literal>"*"</literal>:返回所有子(直接后继)
- <emphasis>元素</emphasis> 结点的序列。这个序列会按"文档顺序"包含元素,
- 也就是说,按照每个XML结点的表现形式的第一个字符的顺序出现
- (在一般实体的扩展之后)。</para>
- </listitem>
-
- <listitem>
- <para><literal>"**"</literal>:返回所有后继 <emphasis>元素</emphasis>
- 结点的序列。这个序列按文档顺序包含元素。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@<replaceable>attName</replaceable>"</literal>,
- <literal>"@<replaceable>prefix</replaceable>:<replaceable>attrName</replaceable>"</literal>:
- 作为一个大小为1,包含属性结点的序列的形式,返回元素的属性名
- <literal><replaceable>attName</replaceable></literal>,如果属性不存在时,
- 作为一个空序列返回(所以来检查属性是否存在,可以使用
- <literal>foo.@<replaceable>attName</replaceable>[0]??</literal>,
- <emphasis>而不是</emphasis>
- <literal>foo.@<replaceable>attName</replaceable>??</literal>)。
- 正如 <literal>"<replaceable>elementName</replaceable>"</literal> 这个特殊的键,
- 如果序列的长度为1,那么它也当作是第一个子变量。如果没有使用
- <literal><replaceable>prefix</replaceable></literal>,那么它只返回属性,
- 而不使用XML命名空间(尽管你已经设置了默认的XML命名空间)。
- 如果使用了 <literal><replaceable>prefix</replaceable></literal>,
- 它返回仅仅属于关联的 <literal><replaceable>prefix</replaceable></literal>
- 的XML命名空间的属性。前缀的注册是由 <link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- 指令</link> 的 <literal>ns_prefixes</literal> 参数完成的。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@"</literal> 或 <literal>"@*"</literal>
- :返回属于父结点的结点的属性序列,这和XPath中的
- <literal>@*</literal> 是相同。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@qname"</literal>:返回元素的完全限定名
- (比如 <literal>e:book</literal>,和由 <literal>?node_name</literal>
- 返回的本地名 <literal>book</literal> 形成对比)。使用的前缀
- (如 <literal>e</literal>)是基于在当前命名空间用 <literal>ftl</literal>
- 指令的 <literal>ns_prefixes</literal> 参数注册的前缀而选择的,
- 而不受源XML文档中使用的前缀影响。如果你已经设置了一个默认的XML命名空间,
- 那么结点会使用默认的,前缀 <literal>D</literal> 就会被使用了。
- 不属于任何XML命名空间的结点,不使用前缀(尽管你设置过默认的命名空间)。
- 如果结点没有为命名空间注册的前缀,那结果是不存在的变量
- (<literal>node.@@qname??</literal> 是 <literal>false</literal>)。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@markup"</literal>:这会以字符串形式返回一个结点的完整XML标记。
- (完整的XML标记表示它也包含子结点的标记,而且包含子结点的子结点的标记,以此类推)
- 你得到的标记和在源XML文档中的标记相比不是必须的,它只是语义上的相同。特别地,
- 注意CDATA段将会解释成普通文本。也要注意基于你是怎么用包装原生的XML文档的,
- 注释或处理指令结点可能被移除,而且它们将会从源文件的输出中消失。
- 对于在输出XML段的每个XML命名空间,第一个被输出的开始标记将会包含
- <literal>xmlns:<replaceable>prefix</replaceable></literal> 属性,
- 而且那些前缀将会在输出的元素和属性名中使用。这些前缀和使用 <literal>ftl</literal>
- 指令的 <literal>ns_prefixes</literal> 参数注册的前缀相同
- (没有前缀使用 <literal>D</literal>,因为它会和 <literal>xmlns</literal>
- 属性被用来注册默认的命名空间),如果XML命名空间没有定义前缀,
- 那么会使用一个任意未被选择使用的前缀。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@nested_markup"</literal>:这个和 <literal>"@@markup"</literal>
- 相似,但是它返回不包括开放和封闭标记元素的XML标记。对于文档结点,它返回和
- <literal>"@@markup"</literal> 相同的内容。对于其他类型结点(文本,处理指令等),
- 它返回一个空字符串。不像 <literal>"@@markup"</literal>,在输出中没有
- <literal>xmlns:<replaceable>prefix</replaceable></literal> 属性,
- 但是关于在元素和属性名中使用前缀规则是相同的。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@text"</literal>:它返回文本结点(所有后继文本结点,
- 而不是直接子结点)点的值,连接成一个单独的字符串。
- 如果结点没有子文本结点,那么返回的是空字符串。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@start_tag"</literal>:返回元素结点 <link
- linkend="gloss.startTag">开始标签</link> 的标记。
- 正如 <literal>@@markup</literal>,输出和源XML文档相比不是必须的,
- 但是在语义上是相同的。关于XML命名空间
- (输出中的<literal>xmlns:<replaceable>prefix</replaceable></literal>属性等),
- 规则和 <literal>"@@markup"</literal> 是相同的。</para>
- </listitem>
-
- <listitem>
- <para><literal>"@@end_tag"</literal>:返回元素结点 <link
- linkend="gloss.endTag">结束标签</link> 的标记,正如
- <literal>@@markup</literal>,输出和源XML文档相比不是必须的,
- 但是在语义上是相同的。</para>
- </listitem>
-
- <listitem>
- <para><literal>@@attributes_markup</literal>:返回元素结点 <link
- linkend="gloss.attribute">属性</link> 的标记,正如
- <literal>@@markup</literal>,输出和源XML文档相比不是必须的,
- 但是在语义上是相同的。</para>
- </listitem>
- </itemizedlist>
-
- <section>
- <title>结点序列</title>
-
- <para>许多特殊哈希表的键(指的是上面所有的列表),
- 和XPath表达式的结果是结点集(参考<link
- xlink:href="http://www.w3.org/TR/xpath">XPath 推荐</link>),
- 返回结点的序列。</para>
-
- <para>这些结点序列,如果它们只存储一个子变量,也会当作子变量本身。
- 例如,如果 <literal>book</literal> 元素只有一个 <literal>title</literal>
- 子结点,<literal>${book.title[0]}</literal> 和 <literal>${book.title}</literal>
- 是相同的。</para>
-
- <para>返回一个空结点序列是普通的情形。例如,如果一个确定的XML文档,
- <literal>book</literal> 元素没有子元素 <literal>chapter</literal>,
- 那么 <literal>book.chapter</literal> 的结果就是一个空的结点序列。
- 要小心!这也意味着 <literal>book.chaptre</literal>(注意打字错误)
- 也会返回空的结点序列,而且会抛出错误而停止执行。同时,
- <literal>book.chaptre??</literal>(注意打字错误)将会返回
- <literal>true</literal>,因为空的序列存在,所以你不得不使用
- <literal>book.chaptre[0]??</literal> 来检查。</para>
-
- <para>结点序列存储的不是1个结点(但是0或者比1多的结点)
- 也会支持上面所描述的哈希表的键。那就是,下面这些特殊的键都是支持的:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>"<replaceable>elementName</replaceable>"</literal>,
- <literal>"<replaceable>prefix</replaceable>:<replaceable>elementName</replaceable>"</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"@<replaceable>attrName</replaceable>"</literal>,
- <literal>"@<replaceable>prefix</replaceable>:<replaceable>attrName</replaceable>"</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"@@markup"</literal>,
- <literal>"@@nested_markup"</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"@@text"</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"*"</literal>, <literal>"**"</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>"@@"</literal>, <literal>"@*"</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>当在一个包含多于1个结点或0个结点序列里应用上面这些特殊的键中之一的时候,
- 那么对于序列(就是特殊的键可以起作用,比如文本结点对于键 <literal>*</literal>
- 或 <literal>@foo</literal> 将会被略过)中的每个结点,
- 这些特殊的键将会被应用于单独的结点,结果将会从最终的结果中被连接。
- 结果会被以和结点在序列中出现的对应顺序来连接。
- 连接就意味着字符串或序列的连接是基于结果类型之上的。
- 如果特殊的键会得到单独结点的字符串结果,那么对于多个结点的结果就是一个单独的字符串
- (对单独结点的结果进行连接),而如果特殊的键返回单独结点的序列,那么对于多个结点,
- 结果就是一个单独的序列。如果在你应用特殊键的序列中没有结点,那么字符串结果就是空串,
- 而序列结果就是空序列。</para>
-
- <para>XPath表达式可以和结点序列一同使用。然而,对于0或者大于1的结点,
- 因为Xalan对XPath实现的限制,仅仅你使用Jaxen代替Xalan时它才会起作用。</para>
- </section>
- </section>
- </chapter>
-
- <chapter xml:id="xgui_declarative">
- <title>声明的XML处理</title>
-
- <indexterm>
- <primary>XML</primary>
-
- <secondary>declarative processing</secondary>
- </indexterm>
-
- <indexterm>
- <primary>XSLT</primary>
- </indexterm>
-
- <section xml:id="xgui_declarative_basics">
- <title>基本内容</title>
-
- <note>
- <para>这部分使用的DOM树和变量是 <link
- linkend="xgui_expose">前一章节</link> 中做好的。</para>
- </note>
-
- <para>因为XML处理的方法非常必要--这在前面章节中已经展示--
- 编写一个FTL程序来遍历树,为了找到不同种类的结点。而使用声明的方法,
- 宁愿定义如何控制不同种类的结点,之后让 FreeMarker 遍历那棵树,
- 调用你定义的处理器。这个方法对于复杂的XML模式非常有用,
- 相同元素可以作为其他元素的子元素出现。
- 这样的模式的示例就是XHTML和XDocBook。</para>
-
- <para>最经常使用来处理声明方式的指令就是 <link
- linkend="ref.directive.recurse"><literal>recurse</literal> 指令</link>,
- 这个指令获取结点变量,并把它作为是参数,从第一个子元素开始,
- 一个接一个地"访问"所有它的子元素。"访问"一个结点意味着它调用了用户自定义的指令
- (比如宏),它的名字和子结点(<literal>?node_name</literal>)的名字相同。
- 我们这么说,用户自定义指令操作结点。使用用户自定义指令 <emphasis>处理</emphasis>
- 的结点作为特殊变量 <literal>.node</literal> 是可用的。例如,这个FTL:</para>
-
- <programlisting role="template"><#recurse doc>
-
-<#macro book>
- I'm the book element handler, and the title is: ${.node.title}
-</#macro></programlisting>
-
- <para>将会输出(这里已经移除了输出内容中一些烦扰的空白):</para>
-
- <programlisting role="output">I'm the book element handler, and the title is: Test Book
-</programlisting>
-
- <para>如果调用 <literal>recurse</literal> 而不用参数,那么它使用
- <literal>.node</literal>,也就是说,它访问现在处理这个结点的所有子结点。
- 所以这个FTL:</para>
-
- <programlisting role="template"><#recurse doc>
-
-<#macro book>
- Book element with title ${.node.title}
- <#recurse>
- End book
-</#macro>
-
-<#macro title>
- Title element
-</#macro>
-
-<#macro chapter>
- Chapter element with title: ${.node.title}
-</#macro></programlisting>
-
- <para>将会输出(这里已经移除了输出内容中一些烦扰的空白):</para>
-
- <programlisting role="output">Book element with title Test Book
-Title element
-Chapter element with title: Ch1
-Chapter element with title: Ch2
-End book</programlisting>
-
- <para>已经看到了如何来为元素结点定义处理器,但不是为文本结点定义处理器。
- 因为处理器的名字是和它处理的结点名字相同的,作为所有文本结点的结点名字是
- <literal>@text</literal>(参考<link
- linkend="misc.xguiTable">该表</link>),
- 为文本结点定义处理器,可以是这样的:</para>
-
- <programlisting role="template">
-<#macro @text>${.node?html}</#macro>
-</programlisting>
-
- <para>请注意 <literal>?html</literal>。不得不转义HTML文本,
- 因为生成的是HTML格式的输出。</para>
-
- <para>这个模板就是转换XML到完整的HTML:</para>
-
- <anchor xml:id="misc.example.declarativeBookProcessor"/>
-
- <programlisting role="template"><#recurse doc>
-
-<#macro book>
- <html>
- <head>
- <title><#recurse .node.title></title>
- </head>
- <body>
- <h1><#recurse .node.title></h1>
- <#recurse>
- </body>
- </html>
-</#macro>
-
-<#macro chapter>
- <h2><#recurse .node.title></h2>
- <#recurse>
-</#macro>
-
-<#macro para>
- <p><#recurse>
-</#macro>
-
-<#macro title>
- <#--
- We have handled this element imperatively,
- so we do nothing here.
- -->
-</#macro>
-
-<#macro @text>${.node?html}</#macro></programlisting>
-
- <para>将会输出(这里包含了那些烦扰的空白):</para>
-
- <programlisting role="output"> <html>
- <head>
- <title>Test Book</title>
- </head>
- <body>
- <h1>Test Book</h1>
-
-
- <h2>Ch1</h2>
-
-
- <p>p1.1
-
- <p>p1.2
-
- <p>p1.3
-
-
- <h2>Ch2</h2>
-
-
- <p>p2.1
-
- <p>p2.2
-
-
- </body>
- </html>
-
- </programlisting>
-
- <para>请注意,可以在输出中使用<link
- linkend="ref_directive_t">trim 指令</link>,如 <literal><#t></literal>
- 来大幅减少多余的空白。参考:<xref
- linkend="dgui_misc_whitespace"/></para>
-
- <para>你也许会说FTL处理它的这些必要方法可以更短些。那是对的,
- 但是示例XML使用了非常简单的模式,正如之前说过的,
- 声明方法带和XML模式一同来了它的格式,
- 而这个模式关于这里可以出现什么元素是不固定的。所以,
- 介绍元素 <literal>mark</literal>, 应该把文本标记为红色,
- 而和你在哪儿使用它无关;在 <literal>title</literal> 或在
- <literal>para</literal> 中。对于这点,使用声明的方法,你可以增加一个宏:</para>
-
- <programlisting role="template"><#macro mark><font color=red><#recurse></font></#macro></programlisting>
-
- <para>那么 <literal><mark>...</mark></literal> 将会自动起作用。
- 所以对于命令式的XML模式,声明的XML处理确实将会很短,而且更重要的是,
- 对于必要的XML处理,FTL-s会更清晰。但这都依赖于你的决定,什么时候使用哪种方法;
- 不要忘记你可以自由混合两种方法。也就是说,在一个元素处理器中,
- 你可以使用命令式的方法来处理元素的内容。</para>
- </section>
-
- <section xml:id="xgui_declarative_details">
- <title>具体细节</title>
-
- <section>
- <title>默认处理器</title>
-
- <para>对于一些XML结点类型,有默认的处理器,
- 这会处理你不给这些结点定义处理器的结点
- (也就是说,如果没有可用的,和结点名称相同的用户自定义指令)。
- 这里的结点类型,默认的处理器会做:</para>
-
- <itemizedlist>
- <listitem>
- <para>文本结点:打印其中的文本。要注意,在很多应用程序中,
- 这对你来说并不好,因为你应该在你发送它们到输出
- (使用 <literal>?html</literal> 或 <literal>?xml</literal>
- 或 <literal>?rtf</literal> 等,这基于输出的格式)前转义这些文本。</para>
- </listitem>
-
- <listitem>
- <para>处理指令结点:如果你定义了自定义指令,可以通过调用处理器调用
- <literal>@pi</literal>,否则将什么都不做(忽略这些结点)。</para>
- </listitem>
-
- <listitem>
- <para> 注释结点,文档类型结点:什么都不做(忽略这些结点)。</para>
- </listitem>
-
- <listitem>
- <para>文档结点:调用 <literal>recurse</literal>,也就是说,
- 访问文档结点的所有子结点。</para>
- </listitem>
- </itemizedlist>
-
- <para>元素和属性结点通常将会被XML独立机制处理。也就是,
- <literal>@<replaceable>node_type</replaceable></literal> 将会被调用作为处理器,
- 如果它没有被定义,那么错误会阻止模板的处理。</para>
-
- <para>元素结点的情形,这意味着如果你定义了一个称为 <literal>@element</literal>
- 的宏(或其他种类的用户自定义指令),没有其他特定的处理器时,
- 那么它会捕捉所有元素结点。如果你没有 <literal>@element</literal> 处理器,
- 那么 <emphasis>必须</emphasis> 为所有可能的元素定义处理器。</para>
-
- <para>属性结点在 <literal>recurse</literal> 指令中不可见,
- 所以不需要为它们编写处理器。</para>
- </section>
-
- <section>
- <title>访问单独结点</title>
-
- <para>使用<link
- linkend="ref.directive.visit"><literal>visit</literal> 指令</link>
- 可以访问单独的结点,而不是结点的子结点:
- <literal><#visit <replaceable>nodeToVisist</replaceable>></literal>。
- 有时这会很有用。</para>
- </section>
-
- <section>
- <title>XML命名空间</title>
-
- <indexterm>
- <primary>XML namespaces</primary>
-
- <secondary>in declarative processing</secondary>
- </indexterm>
-
- <para>我们说过对于一个元素的处理器,用户自定义指令(比如宏)的名字就是元素的名字。
- 事实上,它是元素的完全限定名:
- <literal><replaceable>prefix</replaceable>:<replaceable>elementName</replaceable></literal>。
- 这个关于 <literal><replaceable>prefix</replaceable></literal> 的使用规则和命令式处理是相同的。
- 因此,用户自定义指令 <literal>book</literal> 仅仅处理不属于任何XML命名空间
- (除非你已经定义了默认的XML命名空间)的 <literal>book</literal> 元素。
- 所以示例XML将会使用XML命名空间 <literal>http://example.com/ebook</literal>:</para>
-
- <programlisting role="unspecified"><book xmlns="http://example.com/ebook">
-<replaceable>...</replaceable></programlisting>
-
- <para>那么FTL就会像这样:</para>
-
- <programlisting role="template"><emphasis><#ftl ns_prefixes={"e":"http://example.com/ebook"}></emphasis>
-
-<#recurse doc>
-
-<#macro "<emphasis>e:</emphasis>book">
- <html>
- <head>
- <title><#recurse .node["<emphasis>e:</emphasis>title"]></title>
- </head>
- <body>
- <h1><#recurse .node["<emphasis>e:</emphasis>title"]></h1>
- <#recurse>
- </body>
- </html>
-</#macro>
-
-<#macro "<emphasis>e:</emphasis>chapter">
- <h2><#recurse .node["<emphasis>e:</emphasis>title"]></h2>
- <#recurse>
-</#macro>
-
-<#macro "<emphasis>e:</emphasis>para">
- <p><#recurse>
-</#macro>
-
-<#macro "<emphasis>e:</emphasis>title">
- <#--
- We have handled this element imperatively,
- so we do nothing here.
- -->
-</#macro>
-
-<#macro @text>${.node?html}</#macro></programlisting>
-
- <para>或者你可以定义一个默认的XML命名空间,
- 那后面部分的模板保持和源XML命名空间相同,比如:</para>
-
- <programlisting role="template"><#ftl ns_prefixes={"<emphasis>D</emphasis>":"http://example.com/ebook"}>
-
-<#recurse doc>
-
-<#macro book>
-<replaceable>...</replaceable></programlisting>
-
- <para>但是这种情形下不要忘了在XPath表达式(我们在默认中没有使用)中,
- 默认的XML命名空间必须通过明确的 <literal>D:</literal> 来访问,
- 因为在XPath中没有前缀的名称通常指代没有XML命名空间的结点。
- 而且注意到命令式的XML处理也是相同的逻辑,如果(当且仅当)没有默认XML命名空间时,
- 元素处理器的名字没有XML命名空间是
- <literal>N:<replaceable>elementName</replaceable></literal>。
- 然而,对于不是元素类型的结点(比如文本结点),你不能在处理器名称中使用前缀
- <literal>N</literal>,因为这些结点在XML命名空间中是没有这些概念的。
- 所以对于示例,文本结点的处理器通常就是 <literal>@text</literal>。</para>
-
- <para>对于更详细的内容,请阅读 <link
- linkend="ref_directive_visit">
- <literal>recurse</literal> 和 <literal>visit</literal></link>
- 指令的参考文档。</para>
- </section>
- </section>
- </chapter>
- </part>
-
- <part xml:id="app">
- <title>附录</title>
-
- <appendix xml:id="app_faq">
- <title>FAQ</title>
-
- <indexterm>
- <primary>FAQ</primary>
- </indexterm>
-
- <qandaset>
- <qandaentry xml:id="faq_jsp_vs_freemarker">
- <question>
- <para>JSP 和 FreeMarker ?</para>
-
- <indexterm>
- <primary>JSP</primary>
- </indexterm>
- </question>
-
- <answer>
- <para>我们比较 FreeMarker 和 JSP 2.0 + JSTL 的组合。</para>
-
- <para>FreeMarker 的优点:</para>
-
- <itemizedlist>
- <listitem>
- <para>FreeMarker 不绑定Servlet,网络/Web环境;它仅仅是通过合并模板和Java对象
- (数据模型)来生成文本输出的类库。你可以在任意地方任意时间来执行模板;
- 不需要HTTP的请求转发或类似的手段,也不需要Servlet环境。
- 出于这些特点你可以轻松的将它整合到任何系统中去。</para>
- </listitem>
-
- <listitem>
- <para>更简洁的语法。看下这个JSP(假设
- <literal><%@ taglib prefix="c"
- uri="http://java.sun.com/jsp/jstl/core"
- %></literal>):</para>
-
- <programlisting role="template"><c:if test="${t}">
- True
-</c:if>
-
-<c:choose>
- <c:when test="${n == 123}">
- Do this
- </c:when>
- <c:otherwise>
- Do that
- </c:otherwise>
-</c:choose>
-
-<c:forEach var="i" items="${ls}">
-- ${i}
-</c:forEach></programlisting>
-
- <para>相等的 FTL:</para>
-
- <programlisting role="template"><#if t>
- True
-</#if>
-
-<#if n == 123>
- Do this
-<#else>
- Do that
-</#if>
-
-<#list ls as i>
-- ${i}
-</#list></programlisting>
- </listitem>
-
- <listitem>
- <para>在模板中没有servlet特定的范围和其它高级技术
- (除非,当然,你可以故意地将它们放入数据模型中)。
- 一开始就是为MVC设计的,它仅仅专注于展示。</para>
- </listitem>
-
- <listitem>
- <para>可以从任意位置加载模板;从类路径下,从数据库中等。</para>
- </listitem>
-
- <listitem>
- <para>默认情况下,数字和日期格式是本地化敏感的。
- 因为我们对用户输出,你所做的仅仅是书写 <literal>${x}</literal>,
- 而不是 <literal><fmt:formatNumber value="${x}"
- /></literal>。</para>
- </listitem>
-
- <listitem>
- <para>易于定义特设的宏和函数。</para>
- </listitem>
-
- <listitem>
- <para>隐藏错误并假装它不存在。丢失的变量和 <literal>null</literal>
- 也不会默认视为 <literal>0</literal>/<literal>false</literal>/空字符串,
- 但会引发错误。<link
- linkend="faq_picky_about_missing_vars">在这里参考更多内容...</link></para>
- </listitem>
-
- <listitem>
- <para>"对象包装"。允许你在模板中以自定义,面向表现的方式来展示对象。
- (比如:<link linkend="xgui_imperative_learn">参看这里</link>,
- 来看看使用这种技术时W3C的DOM结点是如何通过模板展现出来的。)</para>
- </listitem>
-
- <listitem>
- <para>宏和函数仅仅是变量,所以它们可以很容易的作为参数值来传递,
- 放置到数据模型中等,就像其它任意值。</para>
- </listitem>
-
- <listitem>
- <para>第一次访问一个页面时几乎察觉不到的延迟
- (或在它改变之后),因为没有更高级的编译发生。</para>
- </listitem>
- </itemizedlist>
-
- <para>FreeMarker 的缺点:</para>
-
- <itemizedlist>
- <listitem>
- <para>不是一种标准。很少的工具和IDE来集成它,少数的开发者知道它,
- 很少的工业化的支持。(然而,使用合适的设置,
- 大部分JSP标签库可以在 FreeMarker 模板中运行,除非它们基于
- <literal>.tag</literal> 文件。)</para>
- </listitem>
-
- <listitem>
- <para>除了一些视觉上的相似性,它的语法不同于HTML/XML语法规则,
- 这会使得新用户感到混乱(这就是简洁的价值所在)。JSP也不遵循它,
- 只是接近。</para>
- </listitem>
-
- <listitem>
- <para>因为宏和函数仅仅是变量,不正确的指令,
- 参数名和丢失的必须变量仅仅在运行时会被检测到。</para>
- </listitem>
-
- <listitem>
- <para>不能和JSF一起使用。(这在技术上可行,但是没有人来实现它)</para>
- </listitem>
- </itemizedlist>
-
- <para>如果你认为可以用 FreeMarker 在应用程序或遗留的仅支持JSP的框架中来代替JSP,
- 你可以阅读这部分内容:<xref
- linkend="pgui_misc_servlet_model2"/></para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_picky_about_missing_vars">
- <question>
- <para>为什么 FreeMarker 对 <literal>null</literal>
- 和不存在的变量很敏感,如何来处理它?</para>
- </question>
-
- <answer>
- <para>概括一下这点是关于什么的:默认情况下,FreeMarker
- 将试图访问一个不存在的变量或 <literal>null</literal> 值
- (<link linkend="faq_null">这两点是一样的</link>)视为一个错误,
- 这会中断模板的执行。</para>
-
- <para>首先,你应该理解敏感的原因。很多脚本语言和模板语言都能容忍不存在的变量
- (还有 <literal>null</literal>),通常它们将这些变量视为空字符串和/或0,
- 还有逻辑false。这些行为主要有以下几点问题:</para>
-
- <itemizedlist>
- <listitem>
- <para>它潜在隐藏了一些可能偶然发生的错误,就像变量名中的一个错字,
- 或者当模板编写者引用程序员没有放到数据模型中的变量时,
- 或程序员使用了一个不同的名字。人们是容易犯下这种偶然的错误的,
- 而计算机则不会,所以失去这些机会,模板引擎可以显示这些错误则是一个很糟糕的运行方式。
- 尽管你很小心地检查了开发期间模板输出的内容,那也很容易就忽略了如
- <literal><#if hasWarnigs><replaceable>print warnings
- here...</replaceable></#if></literal> 这样的错误,可能永远不会打印警告信息,
- 因为你已经搞乱了变量名(注意到了吗?)。也考虑一下后期的维护,当你以后修改你的应用时,
- 你可能不会每次都重新来仔细检查模板(很多应用程序都有数以百计的模板)。
- 单元测试也不会很好的覆盖到web页面内容(如果设置了它们...);
- 它们最多只会检查web页面中的某些手动设置模式,所以它们通常会光滑地通过,
- 但那些修改实际是有bug的。如果页面执行失败,那么测试人员将会注意,
- 单元测试也会注意(比如整个页面失败),在生产环境中,维护人员会注意
- (假设一些人员检查错误日志)。</para>
- </listitem>
-
- <listitem>
- <para>做了危险的假设。脚本语言或模板引擎对应用程序领域是一无所知的,
- 所以当它决定一些它不知道是 0/false 值时,这是一个相当不负责而且很武断的事情。
- 仅仅因为它不知道你当前银行账户的余额,我们能说是0么?
- 仅仅因为不知道一个病人是否对青霉素过敏,我们就能说他/她不对青霉素过敏?
- 想一想这样错误的暗示信息。展示一个错误提示页面通常要比直接显示错误信息好很多,
- 导致用户端的错误决定。</para>
- </listitem>
- </itemizedlist>
-
- <para>这种情况下(不面对这种问题),不对其敏感那就是隐藏错误假装它不存在了,
- 这样很多用户会觉得方便,但是我们仍然相信在大多数情况下严格对待这个问题,
- 从长远考虑会节省你更多时间并提高软件的质量。</para>
-
- <para>另外一方面,我们意识到你有更好的原因在有些地方不想 FreeMarker 对错误敏感,
- 那么对于这种情况的解决方案如下:</para>
-
- <itemizedlist>
- <listitem>
- <para>通常数据模型中含有 <literal>null</literal> 或可选变量。
- 这种情况下使用 <link linkend="dgui_template_exp_missing">这些操作符</link>。
- 如果你使用它们很频繁的话,那么就要重新考虑一下你的数据模型了,
- 因为过分依赖它们并不是那些难以使用的详细模板的结果,
- 但是会增加隐藏错误和打印任意不正确输出(原因前面已经说过了)的可能性。</para>
- </listitem>
-
- <listitem>
- <para>在一些应用程序中,你也许想显示不完整/损坏的页面,而不是错误页面。
- 这种情况下,你可以 <link linkend="pgui_config_errorhandling">使用另一种错误控制器</link>
- 而不是默认的。自定义的错误控制器可以略过有问题的部分,或者在那儿显示错误指示,
- 而不会中止整个页面的呈现。请注意,尽管错误控制器没有给出变量任意的默认值,
- 显示危急信息的页面也可能会好过显示错误页面。</para>
- </listitem>
-
- <listitem>
- <para>如果页面包含的信息不是至关重要的(比如一些侧栏),
- 另外一个你可能感兴趣的特性是 <link linkend="ref_directive_attempt">
- <literal>attempt</literal>/<literal>recover</literal>
- 指令</link>。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_number_grouping">
- <question>
- <para>为什么 FreeMarker 打印奇怪的数字格式
- (比如 1,000,000 或 1 000 000 而不是 1000000)?</para>
- </question>
-
- <answer>
- <para>FreeMarker 使用Java平台的本地化敏感的数字格式信息。
- 默认的本地化数字格式可能是分组或其他不想要的格式。
- 为了避免这种情况,你不得不使用 <link
- linkend="pgui_config_settings">FreeMarker 设置</link> 中的
- <literal>number_format</literal> 来重写Java平台建议的数字格式,比如:</para>
-
- <programlisting role="unspecified">cfg.setNumberFormat("0.######"); // now it will print 1000000
-// where cfg is a freemarker.template.Configuration object</programlisting>
-
- <para>请注意,人们通常在没有分组分隔符时阅读大数是有些困难的。
- 所以通常建议保留分隔符,而在对"计算机"处理时的情况(分组分隔符会使其混乱),
- 就要使用 <link
- linkend="ref_builtin_c"><literal>c</literal> 内建函数</link> 了。比如:</para>
-
- <programlisting role="template"><a href="/shop/productdetails?id=${<emphasis>product.id?c</emphasis>}">Details...</a></programlisting>
-
- <para>对于计算机,你需要 <literal>?c</literal>,而根据本地化设置,
- 小数分隔符还是要担心。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_number_decimal_point">
- <question>
- <para>为什么 FreeMarker 会不好打印的小数/分组分隔符号
- (比如3.14而不是3,14) ?</para>
- </question>
-
- <answer>
- <para>不同的国家使用不同的小数/分组分隔符号。
- 如果你看到不正确的符号,那么可能你的本地化设置不太合适。
- 设置Java虚拟机的默认本地化或使用 <link
- linkend="pgui_config_settings">FreeMarker 设置</link> 中的
- <literal>locale</literal> 来重写默认本地化。比如:</para>
-
- <programlisting role="unspecified">cfg.setLocale(java.util.Locale.ITALY);
-// where cfg is a freemarker.template.Configuration object</programlisting>
-
- <para>然而有时你想输出一个数字,这个数字不是对用户的,
- 而是对"计算机"的(比如你想在CSS中打印大小),
- 不管页面的本地化(语言)是怎么样的,这种情况你必须使用点来作为小数分隔符。
- 这样就可以使用 <link linkend="ref_builtin_c"><literal>c</literal>
- 内建函数</link>,比如:</para>
-
- <programlisting role="template">font-size: ${<emphasis>fontSize?c</emphasis>}pt;</programlisting>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_number_boolean_formatting">
- <question>
- <para>为什么当我想用如 <literal>${aBoolean}</literal> 格式打印布尔值时,
- FreeMarker 会抛出错误,又如何来修正呢 ?</para>
- </question>
-
- <answer>
- <para>不像是数字,布尔值没有通用的可接受的格式,
- 在相同页面中也没有一个通用的格式。就像当你在HTML页面中展示一件产品是可洗的,
- 你可能不会想给访问者看"Washable:true",而是"Washable:yes"。
- 所以我们强制模板作者(由 <literal>${washable}</literal> 引起错误)去探索用户的感知,
- 在确定的地方布尔值应该来显示成什么。通常我们格式化布尔值的做法是:
- <literal>${washable?string("yes", "no")}</literal>,
- <literal>${caching?string("Enabled", "Disabled")}</literal>,
- <literal>${heating?string("on", "off")}</literal>等。</para>
-
- <para>但有两种情形这里是无用的:</para>
-
- <itemizedlist>
- <listitem>
- <para>当打印布尔值来生成计算机语言输出,那么就想要
- <literal>true</literal>/<literal>false</literal>,使用
- <literal>${<replaceable>someBoolean</replaceable>?c}</literal>。
- (这至少需要 FreeMarker 2.3.20 版本。在那之前,通用的做法是编写
- <literal>${<replaceable>someBoolean</replaceable>?string}</literal>,
- 但这是很危险的,因为它的输出基于当前的布尔值格式设置,默认的是
- <literal>"true"</literal>/<literal>"false"</literal>。)</para>
- </listitem>
-
- <listitem>
- <para>当对很多布尔值进行同一方式的格式化时。这种情形可以设置
- <literal>boolean_format</literal> 设置项
- (<literal>Configuration.setBooleanFormat</literal>) 来影响,
- 从 FreeMarker 2.3.20 版本开始,你可以仅仅编写
- <literal>${<replaceable>someBoolean</replaceable>}</literal>。
- (请注意,这对 <literal>true</literal>/<literal>false</literal>
- 无效 - 你还必须在那儿使用 <literal>?c</literal>。)</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_template_not_found">
- <question>
- <para>FreeMarker 没有找到我的模板
- (<literal>TemplateNotFoundException</literal> 或
- <literal>FileNotFoundException</literal>, <quote>Template not
- found</quote> 错误消息)</para>
- </question>
-
- <answer>
- <para>首先,你应该知道 FreeMarker 是不从文件系统路径直接加载模板的。
- 它使用一个简单虚拟的文件系统可以读取非文件系统资源
- (在jar内部的模板,从数据库表中读取模板等...)。虚拟文件由配置设置项来决定,
- <literal>Configuration.setTemplateLoader(TemplateLoader)</literal>。
- 即便你使用的 <literal>TemplateLoader</literal> 映射到了文件系统,
- 它会有一个包含所有模板的基路径,那就是你不能伸到的虚拟文件系统的根
- (也就是说,绝对路径仍然是相对于虚拟文件系统的)。</para>
-
- <para>解决问题的小窍门:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果你是配置 FreeMarker 的人,请确认你设置了合适的
- <literal>TemplateLoader</literal>。</para>
- </listitem>
-
- <listitem>
- <para>否则,请看未找到模板的错误消息是否包含所使用的
- <literal>TemplateLoader</literal> 的描述。如果没有,
- 那么你使用的是老版本的 FreeMarker,那么请更新版本。得到
- <literal>FileNotFoundException</literal> 而不是
- <literal>TemplateNotFoundException</literal> 也是版本太老,
- 所以你不会得到更多的错误消息。(如果
- <literal>TemplateLoader</literal> 在错误消息中是形如
- <literal>foo.SomeTemplateLoader@64f6106c</literal> 这样的内容,
- 而没有显示相关的参数,你可以请作者定义一个更好的
- <literal>toString()</literal>。)</para>
- </listitem>
-
- <listitem>
- <para>常犯的错误是对基于Servlet的web应用程序使用了
- <literal>FileTemplateLoader</literal> 而不是
- <literal>WebappTemplateLoader</literal>。 它会在一种环境中可用,
- 但是不会作用于在另一种,因为Servlet规范没有承诺资源可以作为普通文本来访问,
- 甚至当 <literal>war</literal> 文件被提取时。</para>
- </listitem>
-
- <listitem>
- <para>要知道当你从其它模板中包含/引入模板时,如果没有以
- <literal>/</literal> 来开始模板名称,那么它就会被解释为相对于包含模板的路径。
- 错误消息会包含全(分解后的)名,所以应该注意这里。</para>
- </listitem>
-
- <listitem>
- <para>检查你是否正在使用 <literal>\</literal>
- (反斜杠) 来代替 <literal>/</literal> (斜杠)。
- (FreeMarker 2.3.22 和之后的版本会在错误消息中警告这点。)</para>
- </listitem>
-
- <listitem>
- <para>作为最后的补救办法,对 <literal>freemarker.cache</literal>
- 类别开启debug级别的日志(在你使用的日志框架中),然后来看还会有什么。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_check_version">
- <question>
- <para>文档中编写了关于特性 <replaceable>X</replaceable>,
- 但是好像 FreeMarker 并不知道它,或者行为和文档中的不同,
- 或者据称已经修复的bug仍然存在。</para>
- </question>
-
- <answer>
- <para>你确定你正在使用的文档和正在使用的 FreeMarker 版本号相同?
- 特别要注意,在线文档是对最新稳定的FreeMarker发布版。你可能使用的是老版本;
- 请更新它。</para>
-
- <para>你确定Java类加载器发现了你期望使用的相同版本的
- <literal>freemarker.jar</literal>?也许 <literal>freemarker.jar</literal>
- 是老版本的。要检查这点,尝试使用 <literal>${.version}</literal>
- 在模板中打印版本号。(如果以"Unknown built-in variable: version" 错误消息结束,
- 那么你使用的是相当相当老的版本。)</para>
-
- <para>如果你怀疑该问题是有多个 <literal>freemarker.jar</literal>,
- 典型的罪魁祸首就是某些模块有Maven或Ivy依赖使用了老的
- <literal>freemarker</literal> group ID, 而不是更为现代的
- <literal>org.freemarker</literal> group ID。因为不同的group ID,
- 不会被Maven或Ivy视为构件冲突,而是把两个版本都引入。这种情况下,
- 不得不去掉 <literal>freemarker</literal> 依赖。</para>
-
- <para>如果你认为文档或 FreeMarker 有错误,请在bug跟踪器或邮件列表中中报告。
- 谢谢你!</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_alternative_syntax">
- <question>
- <para>FreeMarker标签中的 <literal><</literal> 和 <literal>></literal>
- 混淆了编辑器或XML处理器,应该怎么做 ?</para>
- </question>
-
- <answer>
- <para>从 FreeMarker 2.3.4 版本开始,你可以使用
- <literal>[</literal> 和 <literal>]</literal> 来代替
- <literal><</literal> 和 <literal>></literal>。要获取更多细节,
- <link linkend="dgui_misc_alternativesyntax">阅读这里...</link></para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_legal_variable_names">
- <question>
- <para>什么是合法的变量名 ?</para>
-
- <indexterm>
- <primary>variables</primary>
-
- <secondary>names</secondary>
- </indexterm>
- </question>
-
- <answer>
- <para>关于在变量名中使用的字符和变量名的长度,FreeMarker 没有限制,
- 但是为了你的方便,在选择变量名时最好是简单变量引用表达式(参见 <link
- linkend="dgui_template_exp_var_toplevel">这里</link>)。
- 如果你不得不选择一个非常极端的变量名,那也不是一个问题:<link
- linkend="faq_strange_variable_name">参加这里</link>。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_strange_variable_name">
- <question>
- <para>如何使用包含负号(<literal>-</literal>),冒号
- (<literal>:</literal>),点(<literal>.</literal>),或其它特殊字符的
- 变量名(宏名,参数名) ?</para>
- </question>
-
- <answer>
- <para>如果你的变量名很奇怪,比如 <quote>foo-bar</quote>,
- 当你编写如 <literal>${foo-bar}</literal> 的形式时,
- FreeMarker 会曲解你想要的东西。在这种确定的情况下,
- 它会相信你想从 <literal>foo</literal> 中减去 <literal>bar</literal> 的值,
- 这个FAQ例子解释了如何控制这样的情况。</para>
-
- <para>首先,应该清理这些句法问题。关于变量名中使用的字符和变量名的长度,
- FreeMarker没有限制。</para>
-
- <para>如果特殊字符是负号
- (<literal>-</literal>, UCS 0x2D) 或点 (<literal>.</literal>, UCS
- 0x2E) 或冒号 (<literal>:</literal>, UCS 0x3A)中的一种,
- 那么你所要做的就是在这些字符前面放置反斜杠(<literal>\</literal>),
- 比如在 <literal>foo\-bar</literal> (从 FreeMarker 2.3.22 版本开始)。
- 之后 FreeMarker 就会知道你不是想要相同符号的操作符。
- 在你指定未被引号表示的标识符时,这都起作用,比如对宏和函数名称,
- 参数名称,所有种类的变量引用。(请注意,这些转义仅在标识符中起作用,
- 而不是在字符串中。)</para>
-
- <para>当特殊字符不是负号,点,或冒号中的一种时,那就很微妙了。
- 我们来看看有问题的变量,名称为 <quote>a+b</quote>。那么:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果你像读取变量:如果它是子变量或其它,可以编写
- <literal>something["a+b"]</literal> (请记住,
- <literal>something.x</literal> 和
- <literal>something["x"])</literal> 是相等的。如果它是顶级变量,
- 它们可以通过特殊哈希变量来访问,<literal>.vars</literal>,
- 所以你可以编写 <literal>.vars["a+b"]</literal>。很自然地,
- 这个技巧对宏和函数调用有有效:
- <literal><@.vars["a+b"]/></literal>,
- <literal>.vars["a+b"](1, 2)</literal>。</para>
- </listitem>
-
- <listitem>
- <para>如果你想创建或修改变量:所有允许你来创建或修改变量的指令
- (比如 <literal>assign</literal>, <literal>local</literal>,
- <literal>global</literal>, <literal>macro</literal>,
- <literal>function</literal>,等等)允许对目的变量名的引用。
- 比如, <literal><#assign
- foo = 1></literal> 和 <literal><#assign
- "foo" = 1></literal> 是相同的。所以你可以编写如
- <literal><#assign "a+b" = 1></literal> 和
- <literal><#macro "a+b"></literal>。</para>
- </listitem>
-
- <listitem>
- <para>不幸的是,你不能使用这样的变量名(包含不是
- <literal>-</literal>,<literal>.</literal> 和
- <literal>:</literal> 的特殊字符)来作为宏参数名。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_jsp_custom_tag_syntax">
- <question>
- <para>为什么当我尝试使用 <replaceable>X</replaceable> JSP 自定义标签时,
- 得到了 "java.lang.IllegalArgumentException: argument
- type mismatch" ?</para>
- </question>
-
- <answer>
- <para>首先,请更新 FreeMarker,因为 2.3.22 和之后的版本给出了更多有用的错误消息,
- 会给出该问题更好的答案。不管怎样,原因如下。在JSP页面,你对所有参数(属性)值使用引号,
- 如果参数的类型是字符串或布尔值或数字,它不会起作用。
- 但是因为自定义标签在FTL模板中是作为普通用户自定义FTL指令来访问的,
- 你不得不在自定义标签内使用FTL语法规则,而不是JSP规则。因此,根据FTL规则,
- 必须不能对布尔值和数字参数值使用引号,否则它们会被解释成字符串值,
- 当 FreeMarker 尝试传递这些值给自定义标签,而它们需要非字符串值时,
- 这会引起类型不匹配错误。</para>
-
- <para>比如,Struts Tiles的 <literal>insert</literal> 标签参数
- <literal>flush</literal> 是布尔值。在JSP中,正确的语法是:</para>
-
- <programlisting role="template"><tiles:insert page="/layout.jsp" <emphasis>flush="true"</emphasis>/>
-<replaceable>...</replaceable></programlisting>
-
- <para>但是在FTL中,你应该编写:</para>
-
- <programlisting role="template"><@tiles.insert page="/layout.ftl" <emphasis>flush=true</emphasis>/>
-<replaceable>...</replaceable></programlisting>
-
- <para>而且,出于相似的原因,这是错误的:</para>
-
- <programlisting role="template"><tiles:insert page="/layout.jsp" <emphasis>flush="${needFlushing}"</emphasis>/>
-<replaceable>...</replaceable></programlisting>
-
- <para>你应该编写:</para>
-
- <programlisting role="template"><tiles:insert page="/layout.jsp" <emphasis>flush=needFlushing</emphasis>/>
-<replaceable>...</replaceable></programlisting>
-
- <para>(不是 <literal>flush=${needFlushing}</literal>!)</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_servlet_include">
- <question>
- <para>如何像 <literal>jsp:include</literal> 一样的方式引入其它的资源 ?</para>
- </question>
-
- <answer>
- <para>不是使用 <literal><#include ...></literal>,
- 那仅仅是包含另外一个 FreeMarker 模板而不涉及Servlet容器。</para>
-
- <para>因为你要的包含方法是和Servlet相关的,
- 而纯 FreeMarker 是不知道Servlet和HTTP的存在,
- 那是Web应用框架来决定你是否可以这样做和如何来做。
- 比如,在Struts2中,你可以这么来做:</para>
-
- <programlisting role="template"><@s.include value="/WEB-INF/just-an-example.jspf" /></programlisting>
-
- <para>如果Web应用框架对 FreeMarker 的支持是基于
- <literal>freemarker.ext.servlet.FreemarkerServlet</literal> 的,
- 那么你可以这样来做(从 FreeMarker 2.3.15 版本之后):</para>
-
- <programlisting role="template"><@include_page path="/WEB-INF/just-an-example.jspf" /></programlisting>
-
- <para>但是如果Web应用框架提供它自己的解决方案,
- 那么你就可以参考,毕竟它可能会做一些特殊的处理。</para>
-
- <para>更多关于 <literal>include_page</literal> 的信息,可以
- <link linkend="pgui_misc_servlet_include">阅读这里...</link></para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_parameter_unwrapping">
- <question>
- <para>如何给普通Java-method/<literal>TemplateMethodModelEx</literal>/<literal>TemplateTransformModel</literal>/<literal>TemplateDirectiveModel</literal> 的实现传递普通
- <literal>java.lang.*</literal>/<literal>java.util.*</literal>
- 对象 ?</para>
- </question>
-
- <answer>
- <para>不幸的是,对于这个问题没有简单的通用解决方案。
- 问题在于 FreeMarker 的对象包装是很灵活的,当从模板中访问变量时是很棒的,
- 但是会使得在Java端解包时变成一个棘手的问题。比如,
- 很可能将一个非 <literal>java.util.Map</literal> 对象包装称为
- <literal>TemplateHashModel</literal>(FTL哈希表变量)。
- 但是它就不能被解包成 <literal>java.util.Map</literal>,
- 因为没有包装过的 <literal>java.util.Map</literal>。</para>
-
- <para>所以该怎么做呢?基本上有下面两种情况:</para>
-
- <itemizedlist>
- <listitem>
- <para>对于展示目的(比如一种"工具"用来帮助 FreeMarker 模板)
- 指令和方法应该声明它们的形式参数为 <literal>TemplateModel</literal>
- 类型和它的更确切的子接口类型。毕竟,对象包装是对于表面转换数据模型,
- 并服务于展示层的,而这些方法是展示层的一部分。如果你仍然需要普通Java类型,
- 那么可以你可以转向当前 <literal>ObjectWrapper</literal> 的
- <literal>ObjectWrapperAndUnwrapper</literal> 接口
- (可以使用 <literal>Environment.getObjectWrapper()</literal>)。</para>
- </listitem>
-
- <listitem>
- <para>和展示任务(比如,对于业务逻辑层)不相关的方法应该被实现成普通的Java方法,
- 而且不能使用任何 FreeMarker 特定的类,因为根据MVC范例,
- 它们必须独立于展示技术(FreeMarker)。如果这样的方法是从模板中调用的,那么 <link
- linkend="pgui_datamodel_objectWrapper">对象包装</link>
- 的责任就是要保证参数转换到合适的类型。如果你使用了 <link
- linkend="pgui_datamodel_defaultObjectWrapper"><literal>DefaultObjectWrapper</literal></link>
- 或 <link
- linkend="pgui_misc_beanwrapper"><literal>BeansWrapper</literal></link>,
- 那么这就会自动发生。对于 <literal>DefaultObjectWrapper</literal>,如果你 <link
- linkend="topic.defaultObjectWrapperIcI">设置它的
- <literal>incompatibleImprovements</literal> 为 2.3.22</link>,
- 该机制运行得更好。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_nonstring_keys">
- <question>
- <para>为什么在 <literal>myMap[myKey]</literal>
- 表达式中不能使用非字符串的键?那现在应该怎么做 ?</para>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>key type</secondary>
- </indexterm>
- </question>
-
- <answer>
- <para>FreeMarker模板语言(FTL)的 <quote>哈希表</quote> 类型和Java的
- <literal>Map</literal> 是不同的。FTL的哈希表也是一个关联数组,
- 但是它仅仅使用字符串的键。这是因为它是为子变量而引入的
- (比如 <literal>user.password</literal> 中的 <literal>password</literal>,
- 它和 <literal>user["password"]</literal> 是相同的),而变量名是字符串。</para>
-
- <para>所以,在FTL有支持非字符串键的类型之前,你还是不得不转向Java的
- <literal>Map</literal> API。你可以这么来做:
- <literal>myMap?api.get(nonStringKey)</literal>。然而,对于运行
- <literal>?api</literal>,你可能需要配置一下 FreeMarker;
- <link linkend="ref_buitin_api_and_has_api">在这里参考更多...</link></para>
-
- <para>请注意,Java的 <literal>Map</literal>对键的确切类非常专注,
- 所以对于在模板中计算的数字类型的键,你不得不将它们转换成合适的Java类型,
- 否则其中的项就不能被发现。比如,如果你在Map中使用 <literal>Integer</literal>
- 类型的键,那么你应该编写 <literal>${myMap.get(numKey?int)}</literal>。
- 这是由FTL的有意简化的仅有单独数字类型的类型系统导致的非常丑陋的写法,
- 而Java区分很多数字类型。请注意,当键值直接从数据模型
- (也就是说,你不用在模板中使用算数运算来改变它的值)中获取时是不需要转换的,
- 包含当它是方法返回值的情况,而且在包装之前要是合适的类,
- 因为这样解包的结果将会是原始的类型。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_simple_map">
- <question>
- <para>当使用 <literal>?keys</literal>/<literal>?values</literal>
- 遍历Map(哈希表)的内容时,得到了混合真正map条目的
- <literal>java.util.Map</literal> 的方法。当然,只是想获取map的条目。</para>
- </question>
-
- <answer>
- <para>当然是使用了 <literal>BeansWrapper</literal> 或者你自己的对象包装器,
- 或者是它的自定义子类,而它的 <literal>simpleMapWrapper</literal>
- 属性将会置成 <literal>false</literal>。不幸的是,这是默认(出于向下兼容的考虑)的情况,
- 所以在你创建对象包装器的地方,你不得不明确地设置它为 <literal>true</literal>。
- 而且,至少从 2.3.22 版本开始,应用程序应该使用
- <literal>DefaultObjectWrapper</literal> (将 <link
- linkend="topic.defaultObjectWrapperIcI">它的
- <literal>incompatibleImprovements</literal> 设置为 2.3.22</link> - 这很重要),
- 那就不会有这个问题。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_modify_seq_and_map">
- <question>
- <para>在 FreeMarker 模板中如何修改序列(list)和哈希表(maps) ?</para>
-
- <indexterm>
- <primary>modify hashes</primary>
- </indexterm>
-
- <indexterm>
- <primary>modify sequences</primary>
- </indexterm>
-
- <indexterm>
- <primary>sequence</primary>
-
- <secondary>modify</secondary>
- </indexterm>
-
- <indexterm>
- <primary>hash</primary>
-
- <secondary>modify</secondary>
- </indexterm>
- </question>
-
- <answer>
- <para>首先,你也许不想修改序列/哈希表,仅仅是连接(增加)它们中的两个或多个,
- 这就会生成一个新的序列/哈希表,而不是修改已经存在的那个。这种情况下使用 <link
- linkend="dgui_template_exp_sequenceop_cat">序列连接</link> 和 <link
- linkend="dgui_template_exp_hashop_cat">哈希表连接符</link>。而且,你也可以使用 <link
- linkend="dgui_template_exp_seqenceop_slice">子序列操作符</link> 来代替移除序列项。
- 然而,要注意性能的影响:这些操作很快,但是这些哈希表/序列都是后续操作的结果
- (也就是说,当你使用操作的结果作为另外一个操作的输入等情况时),
- 而这些结果的读取是比较慢的。</para>
-
- <para>现在,如果你仍然想修改序列/哈希表,那么继续阅读...</para>
-
- <para>FreeMarker 模板语言并不支持序列/哈希表的修改。它是用来展示已经计算好的东西的,
- 而不是用来计算数据的。要保持模板简洁。但是不要放弃,下面你会看到一些建议和技巧。</para>
-
- <para>如果你能在数据模型构建器的程序和模板之间分离这些工作是最好的,
- 那么模板就不需要来改变序列/哈希表。也许你想重新构思一下你的数据模型了,你会明白这是可能的。
- 但是,很少有对一些复杂但都是和纯展示相关的算法进行需要修改序列/哈希表的这种情况。
- 它很少发生,所以要三思那些计算(或它们其中的部分)是属于数据模型领域的而不是展示领域的。
- 我们假设它们确实是输入展示领域的。比如,你想以一些非常精妙的方式展示一个关键词的索引,
- 这些算法需要你来创建,还有编写一些序列变量。那么你应该做这样的一些事情
- (糟糕的情况包含糟糕的方案):</para>
-
- <programlisting role="template"><#assign caculatedResults =
- 'com.example.foo.SmartKeywordIndexHelper'?new().calculate(keywords)>
-<#-- some simple algorithms comes here, like: -->
-<ul>
- <#list caculatedResults as kw>
- <li><a href="${kw.link}">${kw.word}</a>
- </#list>
-</ul></programlisting>
-
- <para>也就是说,你从模板中去除了展示任务中的复杂部分,
- 而把它们放到了Java代码中。请注意,它不会影响数据模型,
- 所以展示层仍然会和其它的应用逻辑相分离。
- 当然这种处理问题的缺陷就是模板设计者会需要Java程序员的帮助,
- 但是对于复杂的算法这可能也是需要的。</para>
-
- <para>现在,如果你仍然坚持说你需要直接使用 FreeMarker 模板来改变序列/哈希表,
- 这里有两种解决方案,但是请阅读它们之后的警告:</para>
-
- <itemizedlist>
- <listitem>
- <para>你可以通过编写 <literal>TemplateMethodModelEx</literal> 和
- <literal>TemplateDirectiveModel</literal> 的实现类来修改特定类型的序列/哈希表。
- 仅仅只是特定的类型,因为 <literal>TemplateSequenceModel</literal> 和
- <literal>TemplateHashModel</literal> 没有用来修改的方法,
- 所以你需要序列或哈希表来实现一些额外的方法。这个解决方案的一个示例可以在FMPP
- (FMPP 是 FreeMarker-based text file PreProcessor,即基于FreeMarker的文本文件与处理器,
- 用于生成文本文件。可以参考FMPP项目的主页获取更多信息http://fmpp.sourceforge.net ,译者注)
- 中看到。它允许你这样来进行操作(<literal>pp</literal> 存储由FMPP为模板提供的服务):</para>
-
- <programlisting role="template"><#assign a = pp.newWritableSequence()>
-<@pp.add seq=a value="red" /></programlisting>
-
- <para><literal>pp.add</literal> 指令仅仅作用于由
- <literal>pp.newWritableSequence()</literal> 方法创建的序列。
- 因此,模板设计者不能修改一个来自于数据模型的序列。</para>
- </listitem>
-
- <listitem>
- <para>如果你使用了定制的包装器(你可以使用 <literal><@myList.append foo
- /></literal>),那么序列可以有一些方法/指令。
- (而且,如果你使用来配置它,那么它就会暴露出公有的方法,
- 你可以对变量来使用作用于 <literal>java.util.Map</literal> 和
- <literal>java.util.List</literal> 对象的Java API。就像Apache的Velocity一样。)</para>
- </listitem>
- </itemizedlist>
-
- <para>但是要小心,这些解决方案有一个问题:<link
- linkend="dgui_template_exp_sequenceop_cat">序列连接</link>, <link
- linkend="dgui_template_exp_seqenceop_slice">序列切分</link> 操作符
- (比如 <literal>seq[5..10]</literal>) 和 <literal>?reverse</literal>
- 不会复制原来的序列,仅仅只是包装了一下(为了效率),所以,
- 如果源序列后期(一种不正常的混叠效应)改变了,那么结果序列将也会改变。
- 相同的问题也存在于 <link linkend="dgui_template_exp_hashop_cat">哈希表连接</link>
- 的结果;它只是包装了两个哈希表,所以,如果你之前修改了要添加的哈希表,
- 那么结果哈希表将会神奇地改变。作为一种变通方式,在你执行了上述有问题的操作之后,
- 要保证你没有修改作为输入的对象,或者没有使用由上述两点
- (比如,在FMPP中,你可以这样来做:<literal><#assign b =
- pp.newWritableSequence(a[5..10])></literal> 和
- <literal><#assign c = pp.newWritableHash(hashA +
- hashB)></literal>)描述的解决方案提供的方法创建结果的拷贝。
- 当然这很容易丢失,所以再次重申,宁可创建数据模型而不用去修改集合,
- 也不要使用上面展示的显示层的任务助手类。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_null">
- <question>
- <para>关于 null 在 FreeMarker 模板语言是什么样的?<indexterm>
- <primary>null</primary>
- </indexterm></para>
- </question>
-
- <answer>
- <para>FreeMarker 模板语言并不知道Java语言中的 <literal>null</literal>。
- 它也没有关键字 <literal>null</literal>,而且它也不能测试变量是否是
- <literal>null</literal>。当在技术上面对 <literal>null</literal> 时,
- 那么它会将其视作是不存在的变量。比如,如果 <literal>x</literal>
- 在数据模型中是 <literal>null</literal>,而且不会被呈现出来,那么
- <literal>${x!'missing'}</literal> 将会打印出 "missing",你无法辨别其中的不同。
- 而且,比如你想测试是否Java代码中的一个方法返回了 <literal>null</literal>,
- 仅仅像 <literal><#if foo.bar()??></literal> 这样来写即可。</para>
-
- <para>你也许对这后面实现的理由感兴趣,出于展示层的观点,<literal>null</literal>
- 和不存在的东西通常是一样的。这二者之间的不同仅仅是技术上的细节,
- 是实现细节的结果而不是应用逻辑。你也不能将 <literal>null</literal>
- 和其它东西来比较(不像Java语言);在模板中,<literal>null</literal>
- 和其它东西来比较是没有意义的,因为模板语言不进行标识比较
- (当你想比较两个对象时,像Java中的 <literal>==</literal> 操作符),
- 但更常见的是内容的比较(像Java语言中的 <literal>Object.equals(Object)</literal>;
- 它也不会对 <literal>null</literal> 起作用)。
- 而 FreeMarker 如何来别变一些具体的东西和不存在的或未知的东西来比较?
- 或两个不存在(未知的)东西是相等的?当然这些问题无法来回答。</para>
-
- <para>这个不了解 <literal>null</literal> 的方法至少有一个问题。
- 当你从模板中调用Java代码的方法时,你也许想传递 <literal>null</literal>
- 值作为参数(因为方法是设计用于Java语言的,那里是有 <literal>null</literal> 这个概念的)。
- 这种情况下你可以利用 FreeMarker 的一个bug(在我们提供一个传递 <literal>null</literal>
- 值给方法正确的方案前,这个bug我们是不会修复的):如果你指定一个不存在的值作为参数,
- 那么它不会引发错误,但是 <literal>null</literal> 就会被传递给这个方法。就像
- <literal>foo.bar(nullArg)</literal> 将会使用 <literal>null</literal> 作为参数调用
- <literal>bar</literal> 方法,假设没有名为"nullArg"的参数存在。</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_capture">
- <question>
- <para>我该怎么在表达式(作为另外一个指令参数)中使用指令(宏)的输出 ?</para>
- </question>
-
- <answer>
- <para>使用 <literal>assign</literal> 或
- <literal>local</literal> 指令捕捉输出到变量中。比如:</para>
-
- <programlisting role="template"><#assign capturedOutput><@outputSomething /></#assign>
-<@otherDirective someParam=capturedOutput /></programlisting>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_questionmark">
- <question>
- <para>在输出中为什么用"?"来代替字符 <replaceable>X</replaceable> ?</para>
- </question>
-
- <answer>
- <para>这是因为你想打印的字符不能用输出流的 <link
- linkend="gloss.charset">字符集</link> (编码) 来表现,所以Java平台
- (而不是FreeMarker)用问号来代替了会有问题的字符。通常情况,对于输出和模板
- (使用模板对象的 <literal>getEncoding()</literal> 方法)应该使用相同的字符集,
- 或者是更安全的,你通常对输出应该使用UTF-8字符集。
- 对输出流使用的字符集不是由FreeMarker决定的,而是你决定的,当你创建
- <literal>Writer</literal> 对象时,传递了模板的 <literal>process</literal> 方法。</para>
-
- <para>示例:这里在Servlet中使用了UTF-8字符集:</para>
-
- <programlisting role="unspecified">...
-resp.setContentType("text/html; charset=utf-8");
-Writer out = resp.getWriter();
-...
-t.process(root, out);
-...</programlisting>
-
- <para>请注意,问号(或其他替代符号)可能在 FreeMarker 环境之外产生,
- 这种情况上面的做法都没有用了。
- 比如一种糟糕的/错误配置的数据库连接或者JDBC驱动可能带来已经替代过的字符文本。
- HTML形式是另外一种编码问题的潜在来源。
- 在很多地方打印字符串中字符的数字编码是个很好的想法,
- 首先来看看问题是在哪里发生的。</para>
-
- <para>你可以阅读有关字符集和 FreeMarker 的更多信息:<link
- linkend="pgui_misc_charset">在这里...</link></para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_retrieve_calculated_values">
- <question>
- <para>在模板执行完成后,怎么在模板中获取计算过的值 ?</para>
- </question>
-
- <answer>
- <para>首先,要确定你的应用程序设计得很好:模板应该展示数据,
- 而不是来计算数据。如果你仍确定你想这么做,请继续阅读...</para>
-
- <para>当你使用 <literal><#assign x = "foo"></literal>时,
- 那么你不会真正修改数据模型(因为它是只读的,参考:
- <xref linkend="pgui_misc_multithreading"/>),但是在处理
- (参考 <xref linkend="pgui_misc_var"/>)运行时环境
- (参考<link linkend="gloss.environment">environment</link>)创建
- <literal>x</literal> 变量。这个问题就是当 <literal>Template.process</literal>
- 返回时,运行时环境将会被丢弃,因为它是为单独 <literal>Template.process</literal>
- 调用创建的:</para>
-
- <programlisting role="unspecified">// internally an Environment will be created, and then discarded
-myTemplate.process(root, out);</programlisting>
-
- <para>要阻止这个,你可以做下面的事情,是和上面相同的,
- 除了你有机会返回在模板中创建的变量:</para>
-
- <programlisting role="unspecified">Environment env = myTemplate.createProcessingEnvironment(root, out);
-env.process(); // process the template
-TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_assign_to_dynamic_variable_name">
- <question>
- <para>如何分派到(或 <literal>#import</literal> 到)
- 动态构建的变量名(比如存储在其它变量的名称) ?</para>
- </question>
-
- <answer>
- <para>如果你真的不能避免这么做(因为它有些混乱,应该不这么做),
- 你可以动态地以字符串形式构建合适的FTL源代码来解决该问题,
- 然后使用 <link
- linkend="ref_builtin_interpret"><literal>interpret</literal>
- 内建函数</link>。比如,如果你想分派名字存储在 <literal>varName</literal>
- 变量中的变量:</para>
-
- <programlisting role="template"><@"<#assign ${varName}='example'>"?interpret /></programlisting>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_template_uploading_security">
- <question>
- <indexterm>
- <primary>security</primary>
-
- <secondary>user-provided templates</secondary>
- </indexterm>
-
- <para>我能允许用户上传模板吗?安全问题又是什么?</para>
- </question>
-
- <answer>
- <para>通常来说,你不能允许用户上传模板,除非它们是系统管理员或其他受信任的人。
- 因为模板是源代码的一部分,就像 <literal>*.java</literal> 文件一样。
- 如果你仍然想让用户上传模板,这里有些问题你是需要考虑的:</para>
-
- <itemizedlist>
- <listitem>
- <para>Denial-of-Service(DoS,拒绝服务,译者注)攻击:
- 这是轻而易举地就可以创建模板,几乎要永远运行下去(使用循环),
- 或者耗尽内存(通过在循环中来连接字符串)。FreeMarker 不能强制CPU或内存的使用限制,
- 所以这并不是在 FreeMarker 级别中可以解决的问题。</para>
- </listitem>
-
- <listitem>
- <para>数据模型和包装(<literal>Configuration.setObjectWrapper</literal>):
- 数据模型给予访问放置其中的一些对象公共Java API的能力。默认情况下,
- 对象若不是特殊处理类型(<literal>String</literal>,
- <literal>Number</literal>, <literal>Boolean</literal>,
- <literal>Date</literal>, <literal>Map</literal>,
- <literal>List</literal>,数组和其它少数类型)的实例,它们的公共Java API就会暴露。
- 要避免这样的情况,你不得不构建只对模板来说是必须使用的API的数据模型。
- 出于这样的考虑,可以使用 <literal>SimpleObjectWrapper</literal>
- (通过 <literal>Configuration.setObjectWrapper</literal> 或者
- <literal>object_wrapper</literal> 设置) 来纯粹从 <literal>Map</literal>,
- <literal>List</literal>,<literal>Array</literal>,<literal>String</literal>,
- <literal>Number</literal>,<literal>Boolean</literal> 和 <literal>Date</literal>
- 来创建数据模型。或者,你可以实现你自己的非常限制的 <literal>ObjectWrapper</literal>,
- 这样就可以安全访问你的POJO了。</para>
- </listitem>
-
- <listitem>
- <para>模板加载器(<literal>Configuration.setTemplateLoader</literal>):
- 模板可以使用名称(或路径)来加载其它模板,比如
- <literal><#include "../secret.txt"></literal>。为了避免加载敏感的数据,
- 你不得不使用 <literal>TemplateLoader</literal> 来二次检查要加载的文件是可以访问的。
- FreeMarker 会来阻止从模板根路径之外来加载文件的,不管模板加载器,
- 但是根据底层的存储机制,可能存在 FreeMarker 考虑不到的漏洞
- (比如,仅仅作为一个例子,<literal>~</literal> 会跳到用户目录)。要注意
- <literal>freemarker.cache.FileTemplateLoader</literal> 检查符合规范的路径,
- 所以,这也许是这个目的的不错选择,但增加文件扩展名的检查
- (文件必须是 <literal>*.ftl</literal> )也是一个不错的想法。</para>
- </listitem>
-
- <listitem>
- <para>内建函数 <literal>new</literal>
- (<literal>Configuration.setNewBuiltinClassResolver</literal>,
- <literal>Environment.setNewBuiltinClassResolver</literal>):
- 在模板中如 <literal>"com.example.SomeClass"?new()</literal> 样来使用,
- 这对于部分在Java中实现的FTL类库来说很重要,但是在普通模板中是不需要的。
- 而 <literal>new</literal> 并不会实例化不是 <literal>TemplateModel</literal> 类型的类,
- FreeMarker中包含的 <literal>TemplateModel</literal> 类,就是用来创建任意的Java对象。
- 其它"危险的" <literal>TemplateModel</literal> 可以存在于你的类路径中。
- 即便它们没有实现 <literal>TemplateModel</literal> 接口,它的静态初始化块也会执行。
- 为了避免这些问题,你应该使用 <literal>TemplateClassResolver</literal>
- 来限制可以访问的类(可能是基于它们要求的模板),比如
- <literal>TemplateClassResolver.ALLOWS_NOTHING_RESOLVER</literal>。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_implement_function_or_macro_in_java">
- <question>
- <para>如何在Java语言中实现方法或宏而不是在模板语言中?</para>
- </question>
-
- <answer>
- <para>这是不可能的,如果你想编写一个类分别实现
- <literal>freemarker.template.TemplateMethodModelEx</literal> 或
- <literal>freemarker.template.TemplateDirectiveModel</literal>,
- 但还是有相似的东西可以使用,那么在你编写 <literal><#function
- my
- <replaceable>...</replaceable>><replaceable>...</replaceable></#function></literal> 或
- <literal><#macro my
- <replaceable>...</replaceable>><replaceable>...</replaceable></#macro></literal> 的地方,
- 可以使用 <literal><#assign my = "your.package.YourClass
- "?</literal><link
- linkend="ref_builtin_new"><literal>new</literal></link><literal>()></literal> 来代替。
- 请注意,对这种方法使用 <literal>assign</literal> 指令,
- 因为在FreeMarker中函数(和方法)和宏仅仅是普通变量。
- (出于相同的原因,你可以在调用模板之前,在数据模型中放置
- <literal>TemplateMethodModelEx</literal> 或 <literal>TemplateDirectiveModel</literal> 实例,
- 或者当你实例化应用程序时,将它们放置到共享变量的map中去。(参考:
- <literal>freemarker.template.Configuration.setSharedVariable(String,
- TemplateModel)</literal>))</para>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_nice_error_page">
- <question>
- <para><anchor xml:id="misc.faq.niceErrorPage"/> 在基于Servlet的应用程序中,
- 如何在模板执行期间发生错误时,展示一个友好的错误提示页面,而不是堆栈轨迹?</para>
- </question>
-
- <answer>
- <para>首先,使用 <literal>RETHROW_HANDLER</literal> 来代替默认的
- <literal>DEBUG_HANDLER</literal> (要获取更多关于模板异常处理的信息,<link
- linkend="pgui_config_errorhandling">请阅读这里...</link>)。
- 现在,当错误发生时,FreeMarker 也不会打印任何东西到输出中了,
- 所以控制权是在你手中的。在你捕捉到
- <literal>Template.process(<replaceable>...</replaceable>)</literal>
- 抛出的异常之后,基本上你可以执行下面两种策略:</para>
-
- <itemizedlist>
- <listitem>
- <para>调用 <literal>httpResp.isCommitted()</literal>,如果它返回了
- <literal>false</literal>,那么你可以调用 <literal>httpResp.reset()</literal>,
- 之后给访问者打印一个"友好的错误提示页面"。如果返回值是 <literal>true</literal>,
- 那么尝试去完成页面,打印一些清晰的东西给访问者,因为Web服务器的错误,
- 页面的生成被突然中断。那么你不得不打印很多冗余的HTML结束标签,
- 设置颜色和字体大小来保证错误信息在客户端浏览器中是可读的
- (检查在 <literal>src\freemarker\template\TemplateException.java</literal> 中的
- <literal>HTML_DEBUG_HANDLER</literal> 源代码,去看一个示例)。</para>
- </listitem>
-
- <listitem>
- <para>使用全页面的缓冲。这就意味着 <literal>Writer</literal>
- 对象不会逐步将输出送到客户端,而是在内存中缓冲整个页面。
- 因为你给 <literal>Template.process(<replaceable>...</replaceable>)</literal>
- 方法提供了 <literal>Writer</literal> 实例,这是你的责任,
- FreeMarker 不会做什么事情的。比如,你想使用 <literal>StringWriter</literal>,
- 如果 <literal>Template.process(<replaceable>...</replaceable>)</literal>
- 方法以抛出异常而返回,那么就忽略由 <literal>StringWriter</literal> 积累的内容,
- 之后发送一个替代的错误页面,否则你就要在输出中打印 <literal>StringWriter</literal>
- 对象中的内容。使用这种方法你就可以确定不用去处理部分发送的页面,
- 但是由于页面(比如,在生成很长的页面时会很慢,用户会感到很大的响应延迟,
- 而且服务器也会占用大量的内存)的特征,它也会有负面的性能影响。
- 要注意,使用 <literal>StringWriter</literal> 对象并不是最有效的解决方案,
- 在积累的内容增长时,它通常也会重新分配自己的缓冲。</para>
- </listitem>
- </itemizedlist>
- </answer>
- </qandaentry>
-
- <qandaentry xml:id="faq_html_editor_mangles">
- <question>
- <para>我正使用一个可视化的HTML割裂模板标记的编辑器。
- 你们可以改变模板语言的语法来兼容我的编辑器么?</para>
- </question>
-
- <answer>
- <para>我们不会修改标准的版本,因为有很多模板都依赖于它。</para>
-
- <para>我们的观点是这个打断模板代码的编辑器是它自己打断的。
- 一个优秀的编辑器应该可以忽略,而不是割裂它不能理解的东西。</para>
-
- <para>从FreeMarker 2.3.4版本开始,你应该感兴趣你可以使用 <literal>[</literal>
- 和 <literal>]</literal> 来代替 <literal><</literal> 和 <literal>></literal> 了。
- 要获取更多信息,<link linkend="dgui_misc_alternativesyntax">请阅读这里...</link></para>
- </answer>
- </qandaentry>
- </qandaset>
- </appendix>
-
- <appendix xml:id="app_versions">
- <title>版本历史</title>
-
- <section xml:id="versions_2_3_24">
- <title>2.3.24</title>
-
- <para>发布日期: FIXME</para>
-
- <section>
- <title>FTL 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>TODO</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Java 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para><emphasis role="strong">注意!</emphasis>
- 提升需要的最低Java版本从1.4到1.5(也就是Java 5)</para>
- </listitem>
-
- <listitem>
- <para>升级 JavaCC (使用构建过程来生成 FTL 解析器) 从 3.2 到 6.1.2。</para>
- </listitem>
-
- <listitem>
- <para>添加
- <literal>Configurable.getSettingNames(camelCase)</literal>,
- 将返回合法的设置项名称。这对于自动完成等很有用。</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_23">
- <title>2.3.23</title>
-
- <para>发布日期: 2015-07-05</para>
-
- <section>
- <title>FTL 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>列表显示 (<literal>#list</literal>)
- 已经得到了一些专业的方便特性,
- 目标是人们在模板中一遍又一遍做的经典任务。</para>
-
- <itemizedlist>
- <listitem>
- <para>新的 <literal>list</literal> 指令的子指令。
- 它们是 <literal>else</literal> 和
- <literal>items</literal>,来处理0长度列表的特殊情况,
- <literal>sep</literal> 来在迭代项之前插入分隔符。
- 更多细节,请参考 <link
- linkend="ref_directive_list"><literal>list</literal>
- 指令</link>。</para>
- </listitem>
-
- <listitem>
- <para><link linkend="ref_builtins_loop_var">作用于循环变量的新内建函数</link>:
- <literal><replaceable>var</replaceable>?index</literal>
- (废弃
- <literal><replaceable>var</replaceable>_index</literal>),
- <literal><replaceable>var</replaceable>?counter</literal>
- (基于1的索引),
- <literal><replaceable>var</replaceable>?has_next</literal>
- (废弃
- <literal><replaceable>var</replaceable>_has_next</literal>),
- <literal><replaceable>var</replaceable>?is_first</literal>,
- <literal><replaceable>var</replaceable>?is_last</literal>,
- <literal><replaceable>var</replaceable>?item_parity</literal>
- (返回 <literal>"odd"</literal> 或
- <literal>"even"</literal>),
- <literal><replaceable>var</replaceable>?item_parity_cap</literal>,
- <literal><replaceable>var</replaceable>?item_cycle</literal><literal>(<replaceable>...</replaceable>)</literal>,等等。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>添加方便的赋值操作符,仅可以用于赋值指令 which can be used
- (当前是 <literal>#assign</literal>,
- <literal>#global</literal> 和 <literal>#local</literal>):</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>++</literal> 和 <literal>--</literal>:
- <literal><#assign counter++></literal> 和
- <literal><#assign counter = counter +
- 1></literal> 是相等的。</para>
- </listitem>
-
- <listitem>
- <para><literal>+=</literal>, <literal>-=</literal>,
- <literal>*=</literal>, <literal>/=</literal> 和
- <literal>%=</literal>: <literal><#assign counter +=
- 2></literal> 和 <literal><#assign
- counter = counter + 2></literal> 是相等的。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>添加 <literal>then</literal> 内建函数,可以像三元操作符来使用:
- <literal><replaceable>someBoolean</replaceable>?then(<replaceable>whenTrue</replaceable>,
- <replaceable>whenFalse</replaceable>)</literal>。
- 和很多其它语言的三元操作符相似,只有一个参数表达式会被评估。<link
- linkend="ref_builtin_then">更多细节...</link></para>
- </listitem>
-
- <listitem>
- <para>添加 <literal>switch</literal> 内建函数,
- 可以像内联(表达式) switch-case-default 语句来使用:
- <literal><replaceable>someValue</replaceable>?switch(<replaceable>case1</replaceable>,
- <replaceable>result1</replaceable>,
- <replaceable>case2</replaceable>,
- <replaceable>result2</replaceable>, ...
- <replaceable>caseN</replaceable>,
- <replaceable>resultN</replaceable>,
- <replaceable>defaultResult</replaceable>)</literal>,这里
- <literal><replaceable>defaultResult</replaceable></literal> 可以被忽略
- (如果没有任何一个情形匹配上,那么就会有错误)。
- <link linkend="ref_builtin_switch">更多细节...</link></para>
- </listitem>
-
- <listitem>
- <para>对模板语言部分标识符添加驼峰格式个支持(自定义名称不受影响)。
- 例如,现在
- <literal><#noEscape>${x?upperCase}</#noEscape></literal>
- 或 <literal><#setting numberFormat="0.0"></literal> 或
- <literal><#ftl stripText=true></literal> 都是合法的。
- 但在同一个模板中,FreeMarker 会需要你对所有模板语言部分的标识符一直使用相同的命名约定。
- 也可以对所有模板强制使用相同的命名约定,在Java中通过
- <literal>Configuration.setNamingConvention(int)</literal> 来实现。
- 从后期的一些版本中,驼峰格式是推荐的约定,因为Java API用户从模板中调用也会用到它。</para>
- </listitem>
-
- <listitem>
- <para>添加新的 <link linkend="ref_specvar">特殊变量</link>,
- <literal>.current_template_name</literal> 和
- <literal>.main_template_name</literal>。它们废弃了
- <literal>.template_name</literal>,因为当在宏调用时,经常会崩溃。
- 新的 <literal>.current_template_name</literal>
- 通常返回包含引用特殊变量的模板名称,而 <literal>.main_template_name</literal>
- 通常返回最高模板的名称。</para>
- </listitem>
-
- <listitem>
- <para>很小的错误信息改进。比如,当
- <literal><replaceable>someMap</replaceable>[<replaceable>someNumber</replaceable>]</literal>
- 抱怨
- <literal><replaceable>someMap</replaceable></literal> 不是序列或强迫为字符串时,
- 在常见问题的错误消息中添加提示。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复,激活设置
- <literal>incompatible_improvements</literal> 为 2.3.23:
- 有一个长期存在的解析时间规则称为
- <literal>#break</literal>,在FTL源代码中,
- 必须在嵌套的可以打断的指令中出现,比如
- <literal>#list</literal> 或 <literal>#switch</literal>。
- 该检查可以绕过 <literal>#macro</literal> 或
- <literal>#function</literal>,比如: <literal><#list 1..1
- as x><#macro
- callMeLater><#break></#macro></#list><@callMeLater
- /></literal>。激活这个修改之后,它会作为解析时间错误被捕获。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Java 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>添加
- <literal>Configuration.setNamingConvention(int)</literal>。
- 默认情况下,FreeMarker 会自动检测用于模板语言部分的标识符的命名规则
- (遗留的 和 驼峰格式),每个模板都是独立地进行。
- 该设置允许你强制使用一个命名规则。</para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration</literal> (事实上,任意的
- <literal>Configurable</literal>) 设置名称现在可以写作驼峰格式了。
- 例如,如果你从属性文件中配置 FreeMarker,你可以使用
- <literal>defaultEncoding=utf-8</literal> 来代替
- <literal>default_encoding=utf-8</literal>。你也可以混合使用两种命名规则
- (驼峰格式,和传统的蛇形格式),而且
- <literal>Configuration.setNamingConvention(int)</literal> 不影响这个行为。</para>
- </listitem>
-
- <listitem>
- <para>添加
- <literal>Configuration.setTemplateUpdateDelayMilliseconds(long)</literal>
- 和
- <literal>Configuration.getTemplateUpdateDelayMilliseconds()</literal>。
- 它们废弃了 <literal>setTemplateUpdateDelay(int)</literal>,使用第二种方案,
- 会基于Java习惯会导致误解。(而且那也没有成对的 getter 方法)</para>
- </listitem>
-
- <listitem>
- <para>当指定字符串时(在 <literal>java.util.Properties</literal>),
- <literal>template_update_delay</literal> 设置支持时间单元,比如
- <literal>template_update_delay=500 ms</literal>。</para>
- </listitem>
-
- <listitem>
- <para>添加 <literal>Environment.getCurrentTemplate()</literal>
- 方法,它会返回当前执行的模板(相对于主模板)。</para>
- </listitem>
-
- <listitem>
- <para>添加
- <literal>WebappTemplateLoader.setAttemptFileAccess(boolean)</literal>,
- 可以用于禁用遗留的尝试通过直接文件访问加载模板的技巧,那么模板更新就无需重启。
- 禁用URL连接缓存
- (<literal>someURLBasedTemplateLoader.setURLConnectionUsesCaches(false)</literal>,
- 从 <literal>incompatible_improvements</literal> 2.3.21 开始,这是默认的)
- 可能会在现代的Servlet容器中解决这个问题。</para>
- </listitem>
-
- <listitem>
- <para>在 <literal>FreemarkerServlet</literal>
- <literal>TemplatePath</literal> 初始化参数中,路径 (比如
- <literal>/templates</literal>) 可以有
- <literal>?settings(<replaceable>...</replaceable>)</literal>
- 前缀,使用它你可以设置 <literal>TemplateLoader</literal>
- 结果的JavaBean属性。例如:
- <literal><param-value>/templates?settings(attemptFileAccess=false,
- URLConnectionUsesCaches=false)</param-value></literal></para>
- </listitem>
-
- <listitem>
- <para>添加
- <literal>FileTemplateLoader.setEmulateCaseSensitiveFileSystem(boolean)</literal>。
- 当你使用Windows系统开发而需要部署在其它大小写敏感的文件系统平台上时,这是很方便的。
- 默认值是 <literal>false</literal>,而 <literal>true</literal> 只是意味着开发,
- 而不是生产环境安装。默认值可以通过设置
- <literal>org.freemarker.emulateCaseSensitiveFileSystem</literal>
- 系统属性为 <literal>true</literal> 来覆盖。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/424">424</link>]:
- <literal>WebappTemplateLoader</literal> 无法找到存储在
- <literal>WEB-INF/lib/*.jar/META-INF/resources</literal> 下的模板。
- 在这个目录下的文件从 Servlet 3.0 版本开始,可以视为
- <literal>ServletContext</literal> 资源,
- 但是 <literal>WebappTemplateLoader</literal>
- 由于一些内部的把戏而通常不能找到它们。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:如果模板 <quote>文件</quote> 成功读取,但是有
- <literal>IOException</literal> 在读取它的内容中发生,
- 解析器(JavaCC)就当作模板 <quote>文件</quote> 在异常那儿结束,
- 异常被压制。这确实是JavaCC的手段,它会影响很多其它基于JavaCC的语言,
- 但是现在 FreeMarker 在 <literal>Template</literal> 构造方法中添加了变通方法,
- 现在异常会被期望的方式抛出了。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:
- <literal>InvalidReferenceException.FAST_INSTANCE</literal> 可能会偶尔存储
- <literal>Environment</literal> 实例的引用,因此导致无法垃圾回收。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/426/">426</link>]:
- 当设置 <literal>incompatible_improvements</literal> 为
- 2.3.22 时,在模板中的特殊变量引用
- <literal>.template_name</literal> 通常返回主(最高)模板的名称,
- 这是因为在 2.3.22 版本中的疏忽。设置
- <literal>incompatible_improvements</literal> 为
- 2.3.23,恢复老的,向后兼容行为。
- (请注意,我们模仿的老行为是它自己破碎,因为它不能在宏调用时好用;
- 你应该使用
- <literal>.current_template_name</literal> 或
- <literal>.main_template_name</literal> 来代替。)</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/53/">53</link>]:
- 对于在相同继承级别有着很多AST(abstract syntax tree,抽象语法树)
- 结点的模板,模板解析会不正常地慢。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:当模板在后台首次加载进行的同时被替换,老版本的模板会进入缓存,
- 并使用新版本的时间戳来标记,因此在更新延迟配置之后就不能被重新加载了。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:<literal>log_template_exceptions</literal>
- 设置项(在 2.3.22 版本中添加) 可以通过
- <literal>Configurable.setSetting(String, String)</literal>
- API 来设置。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:
- <literal>StringUtil.FTLStringLiteralEnc</literal> 转义了
- <literal>$</literal> (因此生成了一个非法转义) 而没有转义
- <literal>$</literal> 和 <literal>#</literal> 之后的 <literal>{</literal>。
- 而该方法只是 FreeMarker 用于生成错误消息的,它是公有的方法,所以任何人都可以使用。</para>
- </listitem>
-
- <listitem>
- <para>Bugs 修复:各种形式的规范问题(它们仅仅影响 FreeMarker 关心的错误消息)。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>其它修改</title>
-
- <itemizedlist>
- <listitem>
- <para>改进功能(可视化的导航树,使用手册内部的搜索等等...)
- 的现代的使用手册和网站设计,感谢 Evangelia Dendramis。
- (同时现在网站使用和手册一致的相同格式和HTML生成器。)</para>
- </listitem>
-
- <listitem>
- <para>很多细微的使用手册和网站内容更新/改进。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>注意</title>
-
- <para>和 2.3.23 RC1 版本的修改比较:</para>
-
- <itemizedlist>
- <listitem>
- <para>如果模板没有名称,<literal>.current_name_name</literal> 和
- <literal>.main_template_name</literal> 就不存在
- (<literal>null</literal>) 而不在是 <literal>""</literal>。</para>
- </listitem>
-
- <listitem>
- <para>一些很小的错误消息改进</para>
- </listitem>
-
- <listitem>
- <para>文档改良</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_22">
- <title>2.3.22</title>
-
- <para>发布日期: 2015-03-01</para>
-
- <para>请注意,因为 2.3.22 版本已经设计为完全向后兼容之前的 2.3.x 发布版本,
- <emphasis>下面描述的一些改进和修复仅仅当你特殊使用 2.3.22 版本时才有效
- <quote>不兼容的改进</quote></emphasis> (它总是清楚地表示),因为它们可以,
- 有很小的机会,可以破坏已有的应用程序。对于在积极维护中的应用程序,那么允许它们
- 是更佳的选择。请参考 <link
- linkend="pgui_config_incompatible_improvements_how_to_set">如何设置
- <quote>不兼容的改进</quote></link>。</para>
-
- <section>
- <title>FTL 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>新的内建函数:<literal>api</literal> 和
- <literal>has_api</literal>。如果值本身支持该额外的特性,
- <literal><replaceable>value</replaceable>?api</literal> 提供对
- <literal><replaceable>value</replaceable></literal>
- 的API(通常是Java API)的访问,比如
- <literal><replaceable>value</replaceable>?api.<replaceable>someJavaMethod()</replaceable></literal>。这意味着很少使用,当你需要调用对象的Java方法时,
- FreeMarker 暴露于模板的值的设计好的简单视图会隐藏它,并且没有相等的内建函数。
- 比如,当你在数据模型(使用默认的对象包装器)中放置 <literal>Map</literal>,
- <literal>myMap.myMethod()</literal> 在模板中只是翻译成Java中的 <literal>((Method)
- myMap.get("myMethod")).invoke(...)</literal>,因此你不能调用
- <literal>myMethod</literal>。如果你编写了
- <literal>myMap?api.myMethod()</literal> 来代替,这就表示Java中的
- <literal>myMap.myMethod()</literal>。</para>
-
- <para><emphasis>如果可以,依靠FTL类型和相关内建函数的尽可能的能力。使用
- <literal>?api</literal> 也仅仅是最后的手段。</emphasis></para>
-
- <para>使用 <literal>?api</literal> 也要对FTL的 <literal>[]</literal>
- 操作符(比如 <literal>myMap[key]</literal>)非 <literal>String</literal>
- <literal>Map</literal> 键值支持提供一种变通的方式,因为现在你可以编写
- <literal>myMap?api.get(nonStringKey)</literal>。</para>
-
- <para><literal>?api</literal> 不是默认启用的,也不是对所有值都可用。<link
- linkend="ref_buitin_api_and_has_api">请参考更多内容...</link></para>
- </listitem>
-
- <listitem>
- <para>标识符(比如 <literal>someVariable</literal>)现在在任意位置可以包含负号
- (<literal>-</literal>),点(<literal>.</literal>),还有冒号(<literal>:</literal>),
- 但是那些字符<emphasis>必须使用前置的反斜杠来转义</emphasis>(<literal>\</literal>),
- 否则它们将被解释成操作符。比如,要读取名为 <quote>data-id</quote> 的变量,
- 正确的表达式是 <literal>data\-id</literal>,因为 <literal>data-id</literal>
- 将会解释成 <quote>data 减去 id</quote>。这对命名的宏参数也起作用,
- 当你想在捕捉所有参数中接受任意HTML属性时,这是很有用的,比如 <literal><@box
- class="someCssClass" data\-id=product.id /></literal>。
- (当你在宏内枚举捕捉所有参数的名称,当然获取到的键值字符串是
- <literal>"data-id"</literal>,而没有 <literal>\</literal>)</para>
- </listitem>
-
- <listitem>
- <para>添加 <literal>?lower_abc</literal> 和
- <literal>?upper_abc</literal>。这会转换
- <literal>1</literal>,<literal>2</literal>,
- <literal>3</literal>,等等...为字符串
- <literal>"a"</literal>,<literal>"b"</literal>,
- <literal>"c"</literal>,等(或者是 <literal>"A"</literal>,
- <literal>"B"</literal>,<literal>"C"</literal>,等)。当到达
- <literal>"z"</literal> 时,就会继续以
- <literal>"aa"</literal>, <literal>"ab"</literal>,等进行。
- 这和在电子表格应用程序(比如Excel或Calc)中看到的列标签有着相同的逻辑。<link
- linkend="ref_builtin_lower_abc">更多内容...</link></para>
- </listitem>
-
- <listitem>
- <para>添加 <literal>?keep_before_last</literal> 和
- <literal>?keep_after_last</literal>。比如:
- <literal>"foo.bar.txt"?keep_before_last(".")</literal> 返回
- <literal>"foo.bar"</literal>,
- <literal>"foo.bar.txt"?keep_after_last(".")</literal> 返回
- <literal>"txt"</literal>。 (这和
- <literal>?keep_before</literal> 和
- <literal>?keep_after</literal> 作用很相似,
- 但是那些只寻找分隔符第一次出现的地方)</para>
- </listitem>
-
- <listitem>
- <para>在合法的标识符字符集中添加很多丢失的UNICODE字母和数字,比如韩语字母
- (bug 修复:
- [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/129/">129</link>])</para>
- </listitem>
-
- <listitem>
- <para>错误消息质量改进:</para>
-
- <itemizedlist>
- <listitem>
- <para>当调用自定义JSP标签时的一些改进;
- 请在后续介绍它们的章节中查看。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:当本地查询或模板获取出现问题,
- 错误消息仍然在用于请求模板的名字上加引号,而不是真实的模板源文件名称
- (比如 当获取的模板是 <literal>foo.ftl</literal> 时,用
- <literal>foo.ftl</literal> 代替 <literal>foo_en.ftl</literal>,
- 但在后台,是从 <literal>foo_en.ftl</literal> 加载的)。</para>
- </listitem>
-
- <listitem>
- <para><quote>模板未找到</quote> 错误现在更加详细,
- 给出了意外使用 <literal>\</literal> 而不是 <literal>/</literal> 的示意,
- 或者退出 <literal>TemplateLoader</literal> 的根目录。</para>
- </listitem>
-
- <listitem>
- <para>当设置的名称没有被识别时,<literal>#setting</literal>
- 指令给出更多有用的错误消息,并且列出允许的设置名称或者改正的建议。</para>
- </listitem>
-
- <listitem>
- <para>当遇到错误的特殊变量名称
- (<literal>.<replaceable>name</replaceable></literal>),
- 可用的名称列表会显示在错误消息中。</para>
- </listitem>
-
- <listitem>
- <para>当 <literal>Map.get</literal> 或包装的
- <literal>Map</literal> 的
- <literal>Map.containsKey</literal> 抛出
- <literal>ClassCastException</literal> 或
- <literal>NullPointerException</literal>,
- 错误会指向引发问题的FTL表达式(还有一些解释),
- 而不是冒出低级别的运行时错误。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Java 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>对象包装改进:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>DefaultObjectWrapper</literal>,仅仅当它的
- <literal>incompatible_improvements</literal> 设置成 2.3.22 时有效
- (<link linkend="topic.defaultObjectWrapperIcI">如何做的请参考这里...</link>),
- 或者更精确来说,将它的
- <literal>useAdaptersForContainers</literal> 设置成
- <literal>true</literal> (当
- <literal>incompatible_improvements</literal> 设置成 2.3.22 时,
- 它默认就是 <literal>true</literal>):当包装成 <literal>TemplateModel</literal>
- (它是模板可以访问所有值的接口)时,它不再复制 <literal>Map</literal>,
- <literal>List</literal> 和数组,仅仅将它们包装成小的
- <literal>TemplateModel</literal> 适配器,那会达到所有操作的原始对象。
- 包装后的值都是新的 <literal>DefaultMapAdapter</literal>,
- <literal>DefaultListAdapter</literal> 和
- <literal>DefaultArrayAdapter</literal> 类的实例,
- 而不是遗留的(复制) <literal>SimpleHash</literal> 和
- <literal>SimpleSequence</literal> 类。(请注意,很多项目使用单纯的
- <literal>BeansWrapper</literal> 来代替
- <literal>DefaultObjectWrapper</literal>,也通常使用适配器方式,
- 虽然只是它的不同的实现罢了。<literal>DefaultObjectWrapper</literal>
- 的缺点现在已经修复了,它总是推荐 <literal>BeansWrapper</literal>,
- 而 <literal>BeansWrapper</literal> 却有相当迷惑的多类型值,也相当慢。)</para>
-
- <para>在这个修改中,保证尽可能多的向后兼容是很重要的因素,
- 这是一个相当深的修改,也许你想 <link
- linkend="topic.defaultObjectWrapperSwitchToAdapters">回顾一下
- 后果和原因,请参考这里...</link> (但重申一下,该修改默认
- <emphasis>不会</emphasis> 激活,
- 所以仅仅更新 FreeMarker 不会对已有应用程序的稳定性构成威胁)</para>
- </listitem>
-
- <listitem>
- <para>对包装方法而不包装它们的父对象,也不在调用时期检查重载方法选择
- 添加了 <literal>TemplateMethodModelEx
- BeansWrapper.wrap(Object object, Method method)</literal>。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/372/">372</link>]:
- 当一个 <literal>SortedMap</literal> (典型的情况是
- <literal>TreeMap</literal>) 被
- <literal>DefaultObjectWrapper</literal> 包装时,
- 之后从它获取并不存在的字符长度为一的字符串,会有
- <literal>ClassCastException</literal>。要修复这个问题,
- 如果被包装的 <literal>Map</literal> 是
- <literal>SortedMap</literal>,并且被
- <literal>DefaultObjectWrapper</literal> 包装,
- 那么它不会在 <literal>String</literal> 键变成
- <literal>null</literal> 后尝试回退到一个
- <literal>Character</literal> 键。(该修改应该是向后兼容的,
- 因为当 <literal>SortedMap</literal> 有
- <literal>Character</literal> 键时,使用 <literal>String</literal>
- 键的初步尝试引发 <literal>ClassCastException</literal>,
- 那么,这样的 <literal>SortedMap</literal>
- 就不能再作为 FTL 哈希变量被使用了。)</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/368/">368</link>]:
- 仅当 <literal>incompatible_improvements</literal> 设置成 2.3.22 或者是将它新的
- <literal>useAdaptersForContainers</literal> 设置成
- <literal>true</literal>时:键的排序和其它 <quote>自定义</quote>
- <literal>Map</literal> 类型的古怪行为不会再丢失了。相同的标准对
- <literal>List</literal> 来说也是一样的。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的设置,
- <literal>forceLegacyNonListCollections</literal>。当且仅当
- <literal>useAdaptersForContainers</literal> 是
- <literal>true</literal> 时有效。那么,除非设置成
- <literal>true</literal>,
- <literal>java.util.Collection</literal> 不是
- <literal>List</literal> (比如 <literal>Set</literal>)
- 会继续使用 <literal>SimpleSequence</literal> (也就是复制方式)
- 来代替适配器方式。默认值是 <literal>false</literal>,至少到
- <literal>incompatible_improvements</literal> 2.4.0,因为
- <literal>SimpleSequence</literal> 给出了非
- <literal>List</literal> 的索引访问,比如
- <literal>mySet[2]</literal>,看起来很奇怪,
- 但是一些已有的模板会使用这种形式,即便仅仅是个意外。将
- <literal>forceLegacyNonListCollections</literal> 设置为
- <literal>false</literal>,那么对 <literal>Set</literal> 等类型
- 的索引访问就不可能了(<literal>?first</literal> 和
- <literal>?last</literal> 都没用了,但是
- <literal>?size</literal> 仍然有效),所以你可能会想去重新测试老的模板。
- 换句话说,你得到了适配器方式的优点。因此,在新的项目中,强烈建议将
- <literal>forceLegacyNonListCollections</literal> 设置为
- <literal>false</literal>。(适配器方式是由
- <literal>DefaultNonListCollectionAdapter</literal> 来实现的。)</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 <emphasis>试验类型的</emphasis> FTL 类型接口,
- <literal>freemarker.template.TemplateCollectionModelEx</literal>,
- 为 <literal>TemplateCollectionModel</literal> 接口添加了
- <literal>size()</literal>,
- <literal>isEmpty()</literal>,和 <literal>boolean
- contains(TemplateModel)</literal> 方法。当包装
- <literal>java.util.Collections</literal> 时,这些额外的功能区是可用的,
- 但是 FTL 直到现在也不能使用它们,所以就添加了。
- 精确的接口细节标记成试验类型,当设置
- <literal>DefaultObjectWrapper</literal> 的
- <literal>forceLegacyNonListCollections</literal> 属性为
- <literal>false</literal> (参考之前内容)时,
- 它本身的特性已经被 <literal>?size</literal> 所使用。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 <emphasis>试验类型的</emphasis> 接口,
- <literal>freemarker.template.ObjectWrapperAndUnwrapper</literal>。
- 它扩展了 <literal>ObjectWrapper</literal>,带有解包功能。
- 这些功能在 <literal>BeansWrapper</literal> 和它的子类中已经存在很长时间了,
- 比如在 <literal>DefaultObjectWrapper</literal> 中,但是它不能
- <quote>分解出</quote> 到它们的公有接口中,而其它的
- <literal>ObjectWrapper</literal> 可以来实现。这对于
- <literal>TemplateModel</literal> 实现来说是很有用的,它不需要
- <literal>BeansWrapper</literal> (或它的子类),仅仅是解包功能可用。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 <emphasis>试验类型的</emphasis> 接口来实现
- <literal>?api</literal> (请在FTL部门参考相关内容):
- <literal>TemplateModelWithAPISupport</literal>,
- <literal>ObjectAPIWrapper</literal>,
- <literal>RichObjectWrapper</literal>。请注意,该接口是试验类型的,
- <literal>?api</literal> 本身则不是。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> 改进:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>FreemarkerServlet</literal> 现在支持自定义
- JSP EL函数(使用 <literal>function</literal> XML 元素在TLD中定义)。
- 早先则会忽略它们。自定义EL函数可以当作Java方法来调用,比如:
- <literal><#assign
- u=JspTaglibs["/WEB-INF/utils.tld"]> ...
- ${u.truncate(title, 25)}</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:当在自定义标签参数中发生实际和期望的参数类型不匹配时,
- 错误信息没有帮助。在用户从FTL调用JSP标签库时,这是一个频繁发生的问题
- (典型的是 "java.lang.IllegalArgumentException: argument type
- mismatch",没有任何FTL上下文)。现在,错误有了适当的解释,解决方案和
- FTL错误位置/引用。</para>
- </listitem>
-
- <listitem>
- <para>解决了 RFE [<link
- xlink:href="https://sourceforge.net/p/freemarker/feature-requests/113/">113</link>]
- [<link
- xlink:href="https://sourceforge.net/p/freemarker/feature-requests/114/">114</link>]:
- <literal>FreemarkerServlet</literal> 现在可以发现对类加载器可见但是不在
- <literal>WEB-INF/lib/*.jar</literal> 中的
- <literal>META-INF/**/*.tld</literal> 了。要激活这个特性,必须设置额外的TLD查找,
- 在 <literal>FreemarkerServlet</literal> 初始化参数中使用
- <literal>MetaInfTldSources</literal> 和/或
- <literal>ClasspathTlds</literal> (参考该内容描述的
- <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html">
- <literal>FreemarkerServlet</literal> Java API 文档 </link> )。
- 比如,如果从 Eclipse 中使用内嵌的 Servlet 容器来运行应用程序,
- 那么标签库的jar文件不在标准的路径下,但是在和其它依赖一样的类路径下,
- 那么现在可以这么来写:</para>
-
- <programlisting role="unspecified"><init-param>
- <param-name>MetaInfTldSources</param-name>
- <param-value>classpath</param-value>
-</init-param></programlisting>
-
- <para>之后所有的 <literal>META-INF</literal>
- 目录就对类加载器可见,可以用来搜索TLD文件了。</para>
- </listitem>
-
- <listitem>
- <para><literal>MetaInfTldSources</literal> 和
- <literal>ClasspathTlds</literal> 各自也可以被附加到 Java 系统属性
- <literal>org.freemarker.jsp.metaInfTldSources</literal> 和
- <literal>org.freemarker.jsp.classpathTlds</literal> 上,
- 或者被它们的值所替换。因此可以在 Eclipse 运行配置中来调整它们而不需要修改
- <literal>web.xml</literal>。(参考 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html">
- <literal>FreemarkerServlet</literal> 的 Java API 文档</link> 来获得更多内容)</para>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> 现在可以识别
- <literal>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</literal>
- servlet 上下文属性,并且从它里面添加项目到
- <literal>MetaInfTldSources</literal> (上面已有介绍)。</para>
- </listitem>
-
- <listitem>
- <para>添加了 <literal>protected
- FreemarkerServlet.createTaglibFactory()</literal> 方法,
- 允许 <literal>TaglibFactory</literal> 配置的微调。现在它有了一些setter方法,
- 比如 <literal>setObjectWrapper</literal>,
- <literal>setMetaInfTldSource</literal>,等等。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 servlet 初始化参数,
- <literal>BufferSize</literal>。如果响应状态仍然允许,这是通过
- <literal>HTTPServletResponse.setBufferSize()</literal>
- 来设置缓冲区的大小,否则就忽略它。</para>
- </listitem>
-
- <listitem>
- <para>现在 <literal>TemplatePath</literal> servlet 初始化参数
- 支持新的路径类型,就像下面这样,
- <literal>classpath:com/example/myapp/templates</literal>。
- 这和老式的路径
- <literal>class://com/example/myapp/templates</literal> 很相似,
- 但是它使用了线程的上下文类加载器(Thread Context Class Loader)
- 来初始化 <literal>FreemarkerSerlvet</literal>,因此即便
- <literal>freemarker.jar</literal> 不在web应用程序的本地,它也能工作。
- <literal>class://</literal> 也有问题,它使用了
- <literal>FreemarkerSerlvet</literal> 本身(或者它的子类)定义的类加载器。</para>
- </listitem>
-
- <listitem>
- <para>如果 <literal>incompatible_improvements</literal> 设置成
- 2.3.22 (或更高),那么 servlet 初始化参数 <literal>TemplatePath</literal>
- 支持在 <literal>[<replaceable>...</replaceable>]</literal>
- 中间指定的多个逗号分隔的路径,比如
- <literal><param-value>[ WEB-INF/templates,
- classpath:com/example/myapp/templates
- ]</param-value></literal>。这会在内部创建一个
- <literal>freemarker.cache.MultiTemplateLoader</literal>。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 servlet <literal>init-param</literal>,
- <literal>ExceptionOnMissingTemplate</literal>。将它设置成
- <literal>true</literal>,就会改变模板没找到错误的行为,
- 和其它模板异常类型相似(比如 HTTP 500 的
- <quote>Internal Server error</quote>,在很多安装时的响应)。
- 当它是 <literal>false</literal> 时(就是遗留的行为),
- 仅仅得到 HTTP 404 <quote>Not found</quote>。
- 而这也是JSP视图如何工作的方式,这就是一个问题,因为如果MVC视图返回 404,
- 一些框架也把 404 给访问者看。但是转发MVC视图的关键点是访问者要访问一个合法的
- URL 地址,只有这个页面没有视图时,那么就是在服务器端损坏了,此时应该是 500。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的可以覆盖的方法:
- <literal>FreemarkerServlet.createDefaultObjectWrapper()</literal>。
- 这可以用于 <literal>createObjectWrapper()</literal> 通常被覆盖之处,
- 但是没有不情愿关闭相关初始化参数的处理(比如
- <literal>object_wrapper</literal>)。</para>
- </listitem>
-
- <listitem>
- <para>提升的(或修复的)错误日志:现在,日志会写入 FreeMarker
- 自己的日志,而不只是 servlet 容器日志。而且,根据 servlet
- 容器的不同,之前的模板未找到和模板解析错误细节日志有时会丢失。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复,仅仅当
- <literal>incompatible_improvements</literal> 设置成 2.3.22
- (或更高):一些类型的值,放置到 JSP
- <emphasis>page</emphasis> 范围 (通过
- <literal>#global</literal> 或者通过 JSP
- <literal>PageContext</literal> API) 之后可以使用 JSP
- <literal>PageContext</literal> API (典型的情况是在自定义
- JSP 标签内)读取回来,也可能作为 FreeMarker
- <literal>TemplateModel</literal> 对象返回,代替有标准 Java 类型的对象。
- 其它 Servlet 范围不受影响。不大可能有些东西期望这个bug出现。
- FTL 类型受影响的值都列在下面了,要触发这个bug,它们不用在模板中直接被创建
- (比如只是FTL原文或使用
- <literal>?date</literal>/<literal>time</literal>/<literal>datetime</literal>),
- 也不用使用 <literal>DefaultObjectWrapper</literal> 或
- <literal>SimpleObjectWrapper</literal> (或者是它们的子类):</para>
-
- <itemizedlist>
- <listitem>
- <para>FTL 日期/时间/日期时间(date/time/date-time) 值可能作为
- <literal>freemarker.template.SimpleDate</literal> 返回,现在
- 它们作为 <literal>java.util.Date</literal> 返回。</para>
- </listitem>
-
- <listitem>
- <para>FTL 序列值可能作为
- <literal>SimpleSequence</literal> 返回,现在它们则作为期望的
- <literal>java.util.List</literal> 返回。这就代表假设
- <literal>object_wrapper</literal> 配置设置是
- <literal>BeansWrapper</literal> (比如
- <literal>DefaultObjectWrapper</literal>) 的子类,但是实际上,
- 应用程序中的通常情况是使用 FreeMarker 的 JSP 扩展 (反之它也能工作,
- 只是依赖于 <literal>ObjectWrapper</literal> 实现的质量和能力)。</para>
- </listitem>
-
- <listitem>
- <para>FTL 哈希值可能作为
- <literal>SimpleHash</literal> 返回,现在它们则作为期望的
- <literal>java.util.Map</literal> 返回 (再次重申,假设对象包装器是
- <literal>BeansWrapper</literal> 的子类)。</para>
- </listitem>
-
- <listitem>
- <para>FTL 集合值可能作为
- <literal>SimpleCollection</literal> 返回,现在它们则作为期望的
- <literal>java.util.Collection</literal> 返回 (再次重申,假设对象包装器是
- <literal>BeansWrapper</literal> 的子类)。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug 修复:现在 <literal>*.tld</literal> 文件会在
- <literal>WEB-INF/</literal> 下去搜索,并且对所有的子目录进行递归。
- 之前只是直接在 <literal>WEB-INF/</literal> 和
- <literal>WEB-INF/lib/</literal> 目录下搜索。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:在 TLD 的 <literal>name</literal> 和
- <literal>tag-class</literal> 元素中的开头和结尾空白现在被移除了。</para>
- </listitem>
-
- <listitem>
- <para>不必要的行为修复:以防多个TLD映射到相同的标签库URI,现在
- <literal>WEB-INF/**/*.tld</literal> 有着比来自 jar 包或者类路径目录下的
- <literal>META-INF/**/*.tld</literal> 更高优先级。之前是相反的,除了
- <literal>META-INF/lib/*.tld</literal> 可以获得随机优先。JSP 规范 (2.2)
- 明确地指出顺序没有定义,也不应有依赖,如果某些人将 TLD 直接放置到
- <literal>WEB-INF</literal> 下,也是符合逻辑的,那就意味着是在特定的 web
- 应用程序中使用,而不是通常来自依赖的 jar 包的 TLD 被多个 web 应用程序所共享。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:在覆盖的
- <literal>FreemarkerServlet.createConfiguration</literal>
- 中的默认设置不会偶然地被 <literal>FreemarkerServlet</literal>
- 重写回默认值了。这个问题只是对以下这些配置:
- <literal>template_exception_handler</literal>,
- <literal>log_template_exceptions</literal>,
- <literal>object_wrapper</literal>,
- <literal>template_loader</literal>。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:如果有多个 If you had multiple
- <literal>FreemarkerServlet</literal>,在相同的 servlet
- 上下文中,每个都有不同的配置设置,那么会导致失灵。
- (通常来说,只能有一个, 就像只有一个 servlet 来处理
- <literal>*.jsp</literal>。)</para>
- </listitem>
-
- <listitem>
- <para>从 FreeMarker 构件和 XML 实体解析器中移除了所有的
- <literal>xsd</literal> 文件
- (<literal>web-app</literal> 和 <literal>taglib</literal>
- 概要),因为它们在 XML 解析中没有用。</para>
- </listitem>
-
- <listitem>
- <para>普遍改进了实现质量
- (可维护性,错误消息,性能 bug 修复,测试覆盖) 和更好的 API 文档。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Logging 功能改进:</para>
-
- <itemizedlist>
- <listitem>
- <para>就像之前的,当自动选择日志库(默认行为),如果Log4j可用,
- 那么 FreeMarker 选择 Log4j。但是现在,变成了
- <literal>log4j-over-slf4j</literal>, FreeMarker 会直接使用
- SLF4J 来替代。(这个修复了日志位置指向 FreeMarker
- 日志适配器类而不是真实调用地方的问题。)</para>
- </listitem>
-
- <listitem>
- <para>FreeMarker 现在可以识别
- <literal>org.freemarker.loggerLibrary</literal> 系统属性,
- 它指定了使用那个日志工具,比如
- <literal>java <replaceable>...</replaceable>
- -Dorg.freemarker.loggerLibrary=SLF4J</literal>。该选项废弃了
- <literal>Logger.selectLoggerLibrary(int)</literal>,因为它天生不可靠
- (因为不能很好地控制类初始化顺序)。该系统属性有比
- <literal>Logger.selectLoggerLibrary</literal> 更高的优先级。</para>
- </listitem>
-
- <listitem>
- <para>普遍改进了实现质量 (当某些东西执行失败时更多的打印信息,等等...)。</para>
- </listitem>
-
- <listitem>
- <para>新的配置设置:
- <literal>log_template_exceptions</literal>
- (<literal>Configuration.setLogTemplateExceptions(boolean)</literal>)。
- 它指定了如果 <literal>TemplateException</literal> 被模板执行抛出,
- 是否该被 FreeMarker 记下日志。为了向后兼容,默认值是
- <literal>true</literal>,但是却导致了在正确编写的程序中,将异常记录两遍,
- 因为 <literal>TemplateException</literal> 被公共的 FreeMarker API 抛出时,
- 也被调用者记下日志(即便仅仅引发的异常是更高级别的异常)。因此,
- 在现代的应用程序中,应该将它设置为
- <literal>false</literal>。(请注意,该设置对
- <literal>#attempt</literal>/<literal>#recover</literal>
- 捕捉到的异常日志记录没有影响;它们通常都是被记录的。)</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><literal>Environment</literal> 和自定义指令相关的改进:</para>
-
- <itemizedlist>
- <listitem>
- <para>添加了
- <literal>Environment.getCurrentDirectiveCallPlace()</literal>,
- 当从自定义指令(比如从 <literal>TemplateDirectiveModel.execute()</literal>)
- 中调用时它会返回 <literal>DirectiveCallPlace</literal> 对象。
- <literal>DirectiveCallPlace</literal> 对象可以关联任意对象到模板中的指令调用,
- 这可以用于调用绑定的缓存(比如非动态缩小嵌套内容)。可以参考 Java API
- 文档获取关于 <literal>DirectiveCallPlace</literal> 的更多内容。</para>
- </listitem>
-
- <listitem>
- <para>添加了
- <literal>Environment.getMainTemplate()</literal>。废弃了含糊的
- (而且经常损坏的: [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/145/">145</link>])
- <literal>Environment.getTemplate()</literal>。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>模板加载:</para>
-
- <itemizedlist>
- <listitem>
- <para>添加了新的 <literal>Configuration</literal> 设置项,
- <literal>template_lookup_strategy</literal>
- (<literal>Configuration.setTemplateLookupStrategy(TemplateLookupStrategy)</literal>)。
- 当模板被请求时,这允许尝试自定义 <literal>TemplateLoader</literal>
- 级别的名称。比如,使用它可以定义自定义本地化序列查找,来代替默认的
- (比如查找:
- <literal>foo_de_LU_MAC.ftl, foo_de_LU.ftl,
- foo_de.ftl,</literal><literal> foo.ftl</literal>)。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的
- <literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal>
- 参数, <literal>Object customLookupCondition</literal>。
- 该参数可以用于自定义的
- <literal>TemplateLookupStrategy</literal> 从请求的名称中
- (和基于位置的默认查找策略相似)去推断真实的模板名称。例如,
- 在一个多域的Web站点中,可能会想要为一个域定义一些专用的模板,
- 那么使用这个域名作为自定义查询的条件。之后,当
- <literal>foo.ftl</literal> 被请求时,自定以的
- <literal>TemplateLookupStrategy</literal> 可以首先查找
- <literal>@somedomain.com/foo.ftl</literal>,之后再查找
- <literal>@default/foo.ftl</literal>。参考相关的
- <literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal>
- 重载的 JavaDoc 来获取更多细节;请注意,那里有关于
- <literal>customLookupCondition</literal> 的
- <literal>hashCode</literal> 和
- <literal>equals</literal> 的需求。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的 <literal>Configuration</literal> 设置,
- <literal>template_name_format</literal>
- (<literal>Configuration.setTemplateNameFormat(TemplateNameFormat)</literal>)。
- 它允许指定 FreeMarker 使用的命名规则。现在,不再允许自定义实现了,
- 仅可以选择在
- <literal>TemplateNameFormat.DEFAULT_2_3_0</literal> (默认的) 和
- <literal>DEFAULT_2_4_0</literal> (推荐的,至少对新项目选择)之中选择。
- <literal>DEFAULT_2_4_0</literal> 有一些优点,但是不能完全向后兼容
- (可是很多应用程序不会被影响)。 典型的错误是使用反斜杠来代替斜杠,
- 或者从根路径开始,它给出
- <literal>MalformedTemplateNameFormatException</literal>
- 来代替 <literal>TempalteNotFoundException</literal>。
- 它允许概要名称以单独的
- <literal>:</literal> 来结束,代替了
- <literal>://</literal> (这也是支持的),比如在
- <literal>classpath:foo/bar.ftl</literal> 中。它修复了很多遗留问题
- (bugs),大多数都是和翻译特殊步骤之后的 <literal>..</literal> 相关,
- 比如 <literal>.</literal> 或 <literal>*</literal>。请在 <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/cache/TemplateNameFormat.html#DEFAULT_2_4_0">
- <literal>TemplateNameFormat.DEFAULT_2_4_0</literal>的 Java API 文档</link>
- 中参考各种相异之处。</para>
- </listitem>
-
- <listitem>
- <para><literal>ClassTemplateLoader</literal> 现在可以由指定的
- <literal>ClassLoader</literal> 来直接创建了,而不再是指定一个基本的
- <literal>Class</literal>。也就是说,现在有
- <literal>ClassTemplateLoader(ClassLoader, String)</literal> 构造方法,也有
- <literal>Configuration.setClassLoaderForTemplateLoading(ClassLoader,
- String)</literal> 方法。</para>
- </listitem>
-
- <listitem>
- <para>添加了新的异常,当获取模板时,
- <literal>TemplateNotFoundException</literal>,现在使用它代替了
- <literal>TemplateNotFoundException</literal>。因为它继承自
- <literal>TemplateNotFoundException</literal>,那么这个修改是向后兼容的。
- 主要目标是回答常见误解,模板路径就是真实的文件路径。
- 而这个新的异常,也有好处,它可以给出额外的 FreeMarker 特定的关于错误的信息,
- 比如现在它有
- <literal>getTemplateName()</literal> 和
- <literal>getCustomLookupCondition()</literal>
- 方法。</para>
- </listitem>
-
- <listitem>
- <para><literal>Template</literal> 现在有
- <literal>getSourceName()</literal> 方法,作为
- <literal>getName()</literal> 的补充。它们两个返回相同的没有本地化的查询或获取
- (在名称中的 <literal>*</literal>)或者其它激活参与的查询策略。
- 但当
- <literal>getSourceName()</literal> 给出的名称是模板从
- <literal>TemplateLoader</literal> 真正加载的时候,而
- <literal>getName()</literal> 返回 (也通常是这么返回的)
- 的名称是模板被请求的(规范化的形式)。<literal>getName()</literal>
- 被用于所有地方(比如相对包含的解决),除了错误消息中的位置信息,现在使用
- <literal>getSourceName()</literal>。而且
- <literal>TemplateException</literal> 现在也有
- <literal>getSourceName()</literal> 方法。</para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal>
- 重载现在对 <literal>locale</literal> 和 <literal>encoding</literal>
- 参数接受 <literal>null</literal>,此种情况下,在参数被忽略时,
- 它们使用相同的默认值作为重载。</para>
- </listitem>
-
- <listitem>
- <para>SPI 调试实现,请注意:
- <literal>DebugBreak</literal> 指令现在会发送模板的
- <literal>sourceName</literal> 给
- <literal>suspendEnvironmentSpi</literal> 回调,而不再是它的
- <literal>name</literal>。也应该在
- <literal>registerTemplateSpi</literal> 等中使用
- <literal>sourceName</literal>,而不再是
- <literal>name</literal>。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>配置:</para>
-
- <itemizedlist>
- <listitem>
- <para>对于一些设置项,添加了
- <literal>Configuration.unset<replaceable>Xxx</replaceable></literal>
- 和
- <literal>is<replaceable>Xxx</replaceable>ExplicitlySet</literal> 方法。
- 去除一些设置,使它就像
- <literal>set<replaceable>Xxx</replaceable></literal> 从未被调用过一样,
- 那么设置项将会使用默认值来适合当前的
- <literal>incompatible_improvements</literal> 值,将来也会当
- <literal>incompatible_improvements</literal> 改变时,
- 它也做出调整。</para>
- </listitem>
-
- <listitem>
- <para>当从
- <literal>java.util.Properties</literal> (或者通常是使用
- <literal>String</literal>-<literal>String</literal>
- 名值对)中来配置FreeMarker时:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>default</literal> 设置的值现在通过
- <literal>template_exception_handler</literal>,
- <literal>template_storage</literal>,
- <literal>template_loader</literal> (还有新的
- <literal>template_lookup_strategy</literal> 和
- <literal>template_name_format</literal>) 配置项被识别,
- 它会引发
- <literal>Configuration.unset<replaceable>Xxx</replaceable>()</literal>
- 方法的调用。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复:当设置
- <literal>object_wrapper</literal> 为
- <literal>default</literal> (也就是不指定它),它会忽略
- <literal>incompatible_improvements</literal> 并通常会使用
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal>。该修复仅仅当
- <literal>incompatible_improvements</literal> 必须是 2.3.21时有效,
- 因为当默认的对象包装器从
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> 改变成 <literal>new
- DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21).build()</literal>
- 的结果,这是有点不同的单例,因为它有只读的配置设置项和bug修复的重载方法选择规则。
- 要使用
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> 而不管
- <literal>incompatible_improvements</literal> 配置项的值,使用新的
- <literal>default_2_3_0</literal> 值。</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug 修复:更改了
- <literal>localized_lookup</literal> 配置项的值,现在情况模板缓存,
- 所以老的查询结果不会被重用了。
- (当然,这仅仅在已有的运行服务中修改该配置才会有效,这基本是不可能的。)</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>其它内容:</para>
-
- <itemizedlist>
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/145/">145</link>],
- 仅仅当
- <literal>incompatible_improvements</literal> 设置为 2.3.22
- (或更高)时有效: <literal>#include</literal> 和
- <literal>#nested</literal> 不会再更改 <literal>Environment</literal> 的父级
- <literal>Template</literal> (请参考
- <literal>Configurable.getParent()</literal>) 为被包含的
- <literal>Template</literal> 或者是
- <literal>#nested</literal> <quote>返回</quote> 的。因此,
- <literal>Environment</literal> 的父级现在就是主
- <literal>Template</literal>。(主
- <literal>Template</literal> 就是有
- <literal>process</literal>
- 或者 <literal>createProcessingEnvironment</literal>
- 方法被调用来初始化生成输出的 <literal>Template</literal>。)
- 请注意,这仅仅在 <literal>Template</literal> 对象
- (不要和在模板中通过 <literal>#setting</literal> 设置配置项所混淆,
- 那样只是修改<literal>Environment</literal>,而不会被此修复所影响)
- 上直接设置配置项时有效,而且几乎没有人会这么做。也请注意,
- 宏调用不会更改 <literal>Environment</literal> 的父级包含宏定义的
- <literal>Template</literal>,现在那里就没有更改了。</para>
- </listitem>
-
- <listitem>
- <para>Bug 修复 [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/419/">419</link>]:
- 当没有权限读取 Java 系统属性时,FreeMarker 不会在发生失败了,
- 比如在未签名的 applet 程序中使用时。它就是记录一些警告日志。</para>
- </listitem>
-
- <listitem>
- <para><literal>HTML_DEBUG</literal> 和
- <literal>DEBUG</literal>
- <literal>TemplateExceptionHandler</literal> 输出现在包含了比如
- <quote>HTML_DEBUG mode; use RETHROW
- in production!</quote> 这样的警告,这是由于频繁误用引起的。</para>
- </listitem>
-
- <listitem>
- <para>在输出中的模板规范化的一些修改和提升,
- 结果是 FTL 堆栈跟踪指令显示出来了。</para>
- </listitem>
-
- <listitem>
- <para>
- 标记了一些历史上公开过但又是内部的 API 为废弃,
- 所以废弃标志在 IDE 中更可见。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>注意</title>
-
- <para xml:id="topic.defaultObjectWrapperSwitchToAdapters">
- 对于在 <literal>DefaultObjectWrapper</literal> 的容器类型,当它的
- 不兼容改进设置为 2.3.22 时,引入适配器方式的结果和原因:</para>
-
- <itemizedlist>
- <listitem>
- <para>使用新方式 (适配器方式),<literal>Map</literal>
- 的键的顺序不会丢失。复制方式仅仅对 <literal>HashMap</literal> 的子类
- (比如 <literal>LinkedHashMap</literal>) 和
- <literal>SortedMap</literal> (比如
- <literal>TreeMap</literal>) 保留,但对外来的
- <literal>Map</literal> 就不是这样,比如 Guava 的
- <literal>ImmutableMap</literal>。而且,其它任何原生
- <literal>Map</literal> 的古怪行为(例如,大小写敏感的键值查找)
- 也保留下来了。</para>
- </listitem>
-
- <listitem>
- <para>当包装的值从模板回传给 Java 方法时,精确的类型和
- <literal>Map</literal>/<literal>List</literal> 的识别符被保留下来。
- 使用遗留的方式,Java 方法得到了特殊的 FreeMarker 特定类型的
- <literal>Map</literal> 或 <literal>List</literal>
- (作为 <literal>TemplateModel</literal> 的适配器)。</para>
- </listitem>
-
- <listitem>
- <para>性能特点改变,大多为更好,但也基于应用程序而定。
- 如果模板 <emphasis>从数据模型中</emphasis> 读取
- <emphasis>same(!)</emphasis> 记录大约一次或两次(或者根本不),
- 这是很典型的情况,那么适配器方式就有更好的结果,
- 否则遗留的复制方式会更快些(因为它可以从之前的读取中重用包装后的记录),
- 尽管这个减缓当然不是大多数应用程序的关注点。
- 新的适配器方式的性能更加可以预测,因为它没有初始化的
- <quote>spike</quote> 来创建容器复制(对于大集合来说这是相当痛苦的),
- 作为替代,对于数据模型读取数量来说(而不是对集合记录数量),
- 性能是线性比例。</para>
- </listitem>
-
- <listitem>
- <para>如果
- <literal>Map</literal>/<literal>List</literal>/数组在它被包装后更改,
- 那么更改现在就在数据模型中可见了。使用复制的方式,
- 包装的值是原始 <literal>Map</literal>/<literal>List</literal>/数组
- 的浅快照。而有些人故意使用它是不可能的,当切换到适配器时它是一个风险因素。</para>
- </listitem>
-
- <listitem>
- <para>从理论上来说,一些代码(更多是
- <literal>TemplateDirectiveModel</literal> 实现)误以为包装的
- <literal>Map</literal> 是
- <literal>SimpleHash</literal>,而包装的
- <literal>List</literal> 是
- <literal>SimpleSequence</literal> 等等,而它们只是
- <literal>TemplateHashModel</literal> 和
- <literal>TemplateSequenceModel</literal>。这样的代码通常是错误的,
- 但是现在确实中断了,所以它也是一个风险因素。</para>
- </listitem>
-
- <listitem>
- <para>现在包装后的原始对象的精确类型是用于重载方法的选择,
- 那么选择可以是不同的(和使用纯 <literal>BeansWrapper</literal> 是相似的)。
- 找出这些情况的例子是很难的。更改最有可能的是数组,因为使用复制方式,它们
- 被解包成 <literal>List</literal>,而不是原来的数组。由于重载方法机制可以
- 在数组和列表(双向的)之间进行转化,那通常不是一个问题。但是,
- 当重载方法在数组参数位置有可变参数类型,它就不能在不同的数组类型直接做转化,
- 所以这也是一个风险因素。</para>
- </listitem>
-
- <listitem>
- <para><literal>SimpleHash</literal> 和
- <literal>SimpleSequence</literal> 没有废弃。
- 它们仍然用于 FTL 中哈希表和序列的创建,特别是从模板中使用的值被创建,
- 这是推荐的做法,而不是包装已经存在的
- <literal>Map</literal> 或 <literal>List</literal> 或数组。</para>
- </listitem>
-
- <listitem>
- <para><literal>List</literal> 和 <literal>Map</literal>
- 在多线程环境下暴露于模板中现在在多线程,只读访问中关于它们正确的操作,
- 有更大的压力。这是因为适配器没有在从多线程访问它们之前,
- 复制它们的内容到已知的
- <literal>List</literal> 和 <literal>Map</literal>
- 实现 (<literal>HashMap</literal>,
- <literal>ArrayList</literal>,等等)。所以使用自定义的
- So this is mostly a concern with custom
- <literal>List</literal> 和 <literal>Map</literal>
- 实现时,这就是一个主要考虑点,它不像标准的 Java 类那样成熟。
- 请注意,使用纯 <literal>BeansWrapper</literal> 时总是这样,
- 这被很多项目/框架(比如 Struts)所使用,也有很长时间了,
- 所以它不是一个未知领域。</para>
- </listitem>
-
- <listitem>
- <para>当包装的 <literal>List</literal> 是
- <literal>AbstractSequentialList</literal> (比如
- <literal>LinkedList</literal>)时,产生的适配器会为更多的高效枚举
- (进行 <literal>#list</literal>)实现
- <literal>TemplateCollectionModel</literal>,另外,当然是
- <literal>TemplateSequenceModel</literal>。
- <literal>TemplateCollectionModel</literal> 允许 FTL 横向访问列表而不需使用
- 索引来访问元素。使用遗留的复制方式
- <literal>TemplateCollectionModel</literal> 没有被实现,
- 因为它不需要高效枚举。</para>
- </listitem>
-
- <listitem>
- <para>迭代器(当直接放置它们到数据模型时)被包装成
- <literal>DefaultIteratorAdapter</literal>
- 而不是 <literal>SimpleCollection</literal>。这会有两个结果:</para>
-
- <itemizedlist>
- <listitem>
- <para>包装的 <literal>Iterator</literal> 当从模板中被传给 Java 方法时,
- 现在被适当解包成原来的 Java 对象。</para>
- </listitem>
-
- <listitem>
- <para>包装的 <literal>Iterator</literal> (不要和
- <literal>Iterable</literal> 搞混) 不再是线程安全的了,节约一些同步,
- 毕竟暴露相同的 <literal>Iterator</literal>
- 到多个并行模板执行中也没有什么意义。这不是迁移所要关注的,而之前,
- 仅仅只有一个模板执行可以成功(<literal>Iterator</literal> 的
- <quote>内容</quote> 没有被复制,那么第一个访问它的单独拥有者)。
- 该更改只是之前它被保证其它线程会失败(是关于线程安全的),
- 而现在没有这些保证了。</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_21">
- <title>2.3.21</title>
-
- <para>发布日期: 2014-10-12</para>
-
- <para>请注意,从 2.3.21 版本开始,设计上是完全向后兼容之前的 2.3.x 发布版,
- <emphasis>下面描述的一些改进和修复仅仅当要求 2.3.21 版本的
- <quote>不兼容改进</quote> 时可用</emphasis>,
- 因为它们可以有很小的机会去弄坏已有的应用程序。如果相关项目仍然在进行开发,
- 极度推荐允许 2.3.21 "不兼容改进"。参考 <link
- linkend="pgui_config_incompatible_improvements_how_to_set">如何设置
- <quote>不兼容改进</quote></link>。</para>
-
- <para>请注意,我们已经更改了所有权,BSD式许可证改为Apache许可证,2.0版。
- 参考 <link linkend="app_license">新的许可证</link>。</para>
-
- <para>请注意,Java 版本的最低需求已经从 1.2 升级到 1.4。</para>
-
- <section>
- <title>FTL 部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para>改进值域:</para>
-
- <itemizedlist>
- <listitem>
- <para>添加了不包含结尾的值域:
- <literal><replaceable>start</replaceable>..<<replaceable>end</replaceable></literal>
- (也可以写为
- <literal><replaceable>start</replaceable>..!<replaceable>end</replaceable></literal>)。
- <link
- linkend="dgui_template_exp_direct_ranges">更多内容...</link></para>
- </listitem>
-
- <listitem>
- <para>添加了限定长度的值域:
- <literal><replaceable>start</replaceable>..*<replaceable>length</replaceable></literal>:
- 例如, <literal>10..*4</literal> 是 <literal>[10,
- 11, 12, 13]</literal>, <literal>10..*-4</literal> 是
- <literal>[10, 9, 8, 7]</literal>,而
- <literal>10..*0</literal> 则是 <literal>[]</literal>。当这些值域用于切分时,
- 如果被切分的序列或字符串结尾在指定值域长度之前,切分结束不会有错误。
- 比如,从字符串 <literal>s</literal> 中获取前10个字符,如果
- <literal>s</literal> 不到10个字符,则获取更少,可以使用
- <literal>s[0..*10]</literal>。 <link
- linkend="dgui_template_exp_seqenceop_slice">更多内容...</link></para>
- </listitem>
-
- <listitem>
- <para>Square bracket now accepts range values from any
- source, like <literal><#assign r = 1..3>
- ${'foobar'[r]}</literal> will print
- <literal>"oob"</literal>. Earlier it has only supported
- ranges that were specified directly inside the square
- brackets, like <literal>'foobar'[1..3]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>When slicing a sequence with a right-unbounded range,
- it's now allowed to have a range start index that's one
- higher than the last index of the sliced sequence. For
- example, <literal>['x', 'y'][2..]</literal> is not an error
- anymore, but an empty sequence. (Of course, <literal>['x',
- 'y'][3..]</literal> is still an error.)</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?substring(<replaceable>from</replaceable>,
- <replaceable>toExclusive</replaceable>)</literal> and
- <literal><replaceable>someString</replaceable>?substring(<replaceable>from</replaceable>)</literal>
- are now deprecated; use this slicing expression instead:
- <literal><replaceable>someString</replaceable>[<replaceable>from</replaceable>..<<replaceable>toExclusive</replaceable>]</literal>
- and
- <literal><replaceable>someString</replaceable>[<replaceable>from</replaceable>..]</literal>.
- A warning if you are processing XML: Since slicing
- expressions work both for sequences and strings, and XML
- nodes in FTL are typically both sequences and strings at the
- same time, there the equivalent expression is
- <literal><replaceable>someXmlNode</replaceable>?string[<replaceable>from</replaceable>..<<replaceable>toExclusive</replaceable>]</literal>
- and
- <literal><replaceable>exp</replaceable>?string[<replaceable>from</replaceable>..]</literal>,
- because without the <literal>?string</literal> it would
- slice the node sequence instead of the text value of the
- node.</para>
- </listitem>
-
- <listitem>
- <para>If the <literal>incompatible_improvements</literal> in
- the FreeMarker configuration is set to at least 2.3.21,
- right-unbounded ranges become readable (like
- <literal>#list</literal>-able). Earlier they could only be
- used for slicing, and behaved like empty sequences
- otherwise.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New built-in, <literal>?url_path</literal>: This is the
- same as <link linkend="ref_builtin_url">the
- <literal>url</literal> built-in</link>, except that it doesn't
- escape slash (<literal>/</literal>) characters. This meant to be
- used for converting paths (like paths coming from the OS or some
- content repository) that use slash (not backslash!) to a path
- the can be inserted into the path part of an URL.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for string manipulation:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal><replaceable>someString</replaceable>?keep_before(<replaceable>substring</replaceable>[,
- <replaceable>flags</replaceable>])</literal>: <link
- linkend="ref_builtin_keep_before">More...</link></para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?keep_after(<replaceable>substring</replaceable>[,
- <replaceable>flags</replaceable>])</literal>: <link
- linkend="ref_builtin_keep_after">More...</link></para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?remove_beginning(<replaceable>substring</replaceable>)</literal>:
- <link
- linkend="ref_builtin_remove_beginning">More...</link></para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?remove_ending(<replaceable>substring</replaceable>)</literal>:
- <link
- linkend="ref_builtin_remove_ending">More...</link></para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?ensure_starts_with(<replaceable>substring</replaceable>[,
- <replaceable>substitution</replaceable>[,
- <replaceable>flags</replaceable>]])</literal>: <link
- linkend="ref_builtin_ensure_starts_with">More...</link></para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?ensure_ends_with(<replaceable>substring</replaceable>)</literal>:
- <link
- linkend="ref_builtin_ensure_ends_with">More...</link></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>someString</replaceable>?number</literal>
- now recognizes all XML Schema number formats, like
- <literal>NaN</literal>, <literal>INF</literal>,
- <literal>-INF</literal>, plus the Java-native formats
- <literal>Infinity</literal> and
- <literal>-Infinity</literal>.</para>
- </listitem>
-
- <listitem>
- <para>If <literal>incompatible_improvements</literal> in the
- FreeMarker configuration is set to at least 2.3.21,
- <literal><replaceable>someNumber</replaceable>?c</literal> will
- return <literal>"INF"</literal>, <literal>"-INF"</literal> and
- <literal>"NaN"</literal> for positive/negative infinity and IEEE
- floating point Not-a-Number, respectively. These are the XML
- Schema compatible representations of these special values.
- Earlier it has returned what
- <literal>java.text.DecimalFormat</literal> did with US locale,
- none of which was understood by any (common) computer
- language.</para>
- </listitem>
-
- <listitem>
- <para>New built-in:
- <literal><replaceable>someString</replaceable>?boolean</literal>.
- This is for example useful for converting "true" and "false"
- strings coming from XML to real boolean values. <link
- linkend="ref_builtin_boolean">More...</link></para>
- </listitem>
-
- <listitem>
- <para>Date/time/date-time related changes:</para>
-
- <itemizedlist>
- <listitem>
- <para>Added new kind of
- <literal>date_format</literal>/<literal>datetime_format</literal>/<literal>time_format</literal>
- setting values: XML Schema formats, starting with
- <literal>"xs"</literal> and ISO 8601:2004 formats, starting
- with <literal>"iso"</literal>. The format string can be
- continued with various space (or <literal>_</literal>)
- separated options, like <literal>h</literal> or
- <literal>m</literal> or <literal>s</literal> or
- <literal>ms</literal> for setting shown accuracy,
- <literal>nz</literal> or <literal>fz</literal> for setting
- time zone offset visibility, and <literal>u</literal> or,
- <literal>fu</literal> for using UTC time zone . For example,
- to use ISO 8601 with minute precision and without the zone
- offset being shown, set the
- <literal>datetime_format</literal> setting to <literal>"iso
- m nz"</literal>, so then the output will be like
- <literal>2014-09-03T20:56</literal>. <link
- linkend="topic.dateTimeFormatSettings">More...</link></para>
- </listitem>
-
- <listitem>
- <para>Because anything that's accepted as
- <literal>date_format</literal>/<literal>datetime_format</literal>/<literal>time_format</literal>
- setting value can also be used with the
- <literal>?string</literal> and
- <literal>?date</literal>/<literal>?time</literal>/<literal>?datetime</literal>
- build-ins, you can use the new formats like
- <literal>someDate?string.xs</literal> and
- <literal>someString?date.xs</literal>. (For the
- <literal>"xs"</literal> and <literal>"iso"</literal>
- formats, <literal>_</literal> can be used instead of space,
- which means that, for example, you can write
- <literal>lastModified?string.iso_m_u</literal> instead of
- the more verbose <literal>lastModified?string["iso m
- u"]</literal>.)</para>
- </listitem>
-
- <listitem>
- <para>That <literal>"iso"</literal> and
- <literal>"xs"</literal> are now possible
- <literal>date_format</literal>/<literal>datetime_format</literal>/<literal>time_format</literal>
- setting values also means that such values can now be parsed
- too via
- <literal>?date</literal>/<literal>?time</literal>/<literal>?datetime</literal>.
- The main application is with processing XML DOM-s, as there
- values are coming in as strings, and now you can do
- something like <literal>order.confirmDate?date.xs</literal>
- to convert them to real dates.</para>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="ref_builtin_date_iso"><literal>?iso_...</literal>
- built-ins</link> are now deprecated in favor of the new
- setting values described above. They can be set as the
- default date/time/date-time format, seamlessly fit into the
- formatting architecture (and thus can parse strings too),
- and has more/better options (<literal>ms</literal> always
- shows 3 millisecond digits, <literal>fz</literal> for
- forcing showing time zone offset).</para>
- </listitem>
-
- <listitem>
- <para>If the <quote>incompatible improvements</quote>
- configuration setting is at least 2.3.21, the
- <literal>?iso_...</literal> built-ins won't show time zone
- offset for <literal>java.sql.Time</literal> values anymore.
- Most databases store time values that aren't in any time
- zone, but just store hour, minute, second, and decimal
- second field values, so showing the time zone doesn't make
- sense. (Notable exceptions are PostgreSQL "time with time
- zone" columns, where
- <literal><replaceable>mzTime</replaceable>?string.iso_fz</literal>
- could be used.)</para>
- </listitem>
-
- <listitem>
- <para>Added <literal>?is_time</literal>,
- <literal>?is_datetime</literal>,
- <literal>?is_date_only</literal> (should be called
- <literal>?is_date</literal>, but that was already taken) and
- <literal>?is_unknown_date_like</literal> to check the exact
- type of a date-like value.</para>
- </listitem>
-
- <listitem>
- <para><literal>?is_date</literal> is now a deprecated name,
- use <literal>?is_date_like</literal> instead. This is
- because <literal>?is_date</literal> sounds like it checks if
- the value is a date without time part, but actually it also
- returns <literal>true</literal> for time, date-time, and
- unknown date-like values.</para>
- </listitem>
-
- <listitem>
- <para>Added <literal>?date_if_unknown</literal>,
- <literal>?time_if_unknown</literal> and
- <literal>?datetime_if_unknown</literal> built-ins, which
- mark a date-like value with some of the sub-types: date
- without time, time, or date-time, respectively. However, if
- the value already holds this information, the built-in has
- no effect. That is, it will never convert the sub-type of a
- value, it only adds the sub-type if it was unknown.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: ISO 8601 dates (via
- <literal>?iso_...</literal> and <literal>"iso"</literal>
- format settings) now use proleptic Gregorian calendar for
- the years before 1582, rather than Julian calendar. This is
- (indirectly) required by the standard, and it's also how the
- default Sun/Oracle Java XML Schema date/time/dateTime parser
- works.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Error message quality improvements (targeting frequent
- support requests and some error message bugs):</para>
-
- <itemizedlist>
- <listitem>
- <para>Fixed glitch where if an <literal>#if</literal> had
- and <literal>#else</literal> or <literal>#elseif</literal>,
- the
- <literal>#if</literal>/<literal>#else</literal>/<literal>#elseif</literal>
- wasn't hidden in the FTL stack trace when the error was
- inside its nested block.</para>
- </listitem>
-
- <listitem>
- <para>Some new context sensitive hints in undefined variable
- exception error messages.</para>
- </listitem>
-
- <listitem>
- <para>Fixed unclosed directive error messages at end of file
- where the wrong unclosed directive name was reported</para>
- </listitem>
-
- <listitem>
- <para>Better type error messages when accessing XML data
- (applies when wrapped with
- <literal>freemarker.ext.dom</literal>):</para>
-
- <itemizedlist>
- <listitem>
- <para>Trying to use
- <literal>node.<replaceable>noSuchChildNodes</replaceable></literal>
- on a place where scalar value is expected will explain
- that the problem is that you had no matches in the
- constructing XML query.</para>
- </listitem>
-
- <listitem>
- <para>Trying to use
- <literal>node.<replaceable>multipleSuchChildNodes</replaceable></literal>
- on a place where scalar value is expected will explain
- that the problem is that you had multiple matches in the
- constructing XML query.</para>
- </listitem>
-
- <listitem>
- <para>Trying to use
- <literal>node.<replaceable>exactlyOneChildNode</replaceable></literal>
- as number, date/time/date-time or boolean will explain
- that values coming from XML are always strings (text),
- and must be converted explicitly via
- <literal>?number</literal>, <literal>?boolean</literal>,
- <literal>?date.xs</literal>, etc.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Trying to use <literal>obj.someMethod</literal>
- without <literal>()</literal> on a place where method value
- is not expected will recommend calling the method.</para>
- </listitem>
-
- <listitem>
- <para>Trying to use methods like
- <literal>obj.getFoo</literal> or
- <literal>obj.isFoo</literal> without <literal>()</literal>on
- a place where method value is not expected will recommend
- using the <literal>obj.foo</literal> form.</para>
- </listitem>
-
- <listitem>
- <para>Messages are now much more readable when rendered in
- environments that don't obey to line-breaks. (This often
- happens in improperly implemented HTML error pages and logs
- viewers.)</para>
- </listitem>
-
- <listitem>
- <para>Better FTL instruction stack traces:</para>
-
- <itemizedlist>
- <listitem>
- <para>Error messages now contain up to 10 lines of FTL
- stack trace (unless it's on the top of a full FTL stack
- trace), because the FTL stack trace wasn't printed at
- all when the exception was a cause exception in a Java
- stack trace, or when only the value of
- <literal>getMessage()</literal> was printed instead of a
- stack trace.</para>
- </listitem>
-
- <listitem>
- <para>The FTL stack trace is now more self explanatory
- as it contains more text labels.</para>
- </listitem>
-
- <listitem>
- <para>Stack frames that belong to nestings are now
- marked differently, and are filtered out when the stack
- trace wouldn't fit into the error message
- otherwise.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <literal>?substring</literal> has thrown
- low level
- <literal>java.lang.IndexOutOfBoundsException</literal>-s
- instead of more descriptive
- <literal>TemplateModelException</literal>-s with FTL stack
- trace.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Slicing with ranges sometimes thrown low
- level
- <literal>java.lang.IndexOutOfBoundsException</literal>-s
- instead of more descriptive
- <literal>TemplateModelException</literal>-s with FTL stack
- trace.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/402/">402</link>]:
- Fixed misleading parser error message when a directive
- called without its required parameters (like
- <literal><#list></literal>) was reported as unknown
- directive.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/222/">222</link>]:
- Poor quality error message when
- <literal><replaceable>someString</replaceable>[<replaceable>someIndex</replaceable>]</literal>
- fails with string index out of bounds.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/27/">27</link>]:
- Not very good quality error messages when
- <literal>#import</literal>-ing a template whose parsing
- fails.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New <literal>include</literal> directive option,
- <literal>ignore_missing=<replaceable>boolean</replaceable></literal>.
- When this is set to <literal>true</literal>, and the template to
- include is missing, the error will be silently ignored, and
- nothing will be included.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>setting</literal> directive can now set the
- <literal>output_encoding</literal> setting.</para>
- </listitem>
-
- <listitem>
- <para>New special variable: <literal>.locale_object</literal>.
- This is like <literal>.locale</literal>, except that it's a
- <literal>java.util.Locale</literal> object, not a string. This
- is handy if you want to pass the current locale to Java
- methods.</para>
- </listitem>
-
- <listitem>
- <para>If <literal>incompatible_improvements</literal> in the
- FreeMarker configuration is set to at least 2.3.21, hash
- <emphasis>literals</emphasis> that repeat keys now only have the
- key once with <literal>?keys</literal>, and only has the last
- value associated to that key with <literal>?values</literal>.
- This is consistent with the behavior of
- <literal><replaceable>hash</replaceable>[<replaceable>key</replaceable>]</literal>
- and how maps work in Java.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <literal>?is_enumerable</literal> has returned
- <literal>true</literal> for Java methods get from Java objects,
- despite that those values aren't <literal><#list
- ...></literal>-able. (This is actually a historical quirk of
- <literal>BeansWrapper</literal>, not a bug in
- <literal>?is_enumerable</literal>, but now
- <literal>?is_enumerable</literal> is aware of this exceptional
- case.)</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/257/">257</link>]:
- The result value of <literal>?matches</literal> wasn't
- <quote>reentrant</quote>. For example, you couldn't list the
- matches inside another listing where you are also listing
- exactly the same result value (stored in a common variable), as
- they would consume from the same iterator. Most importantly,
- even accessing the <literal>?size</literal> of the same result
- value has terminated the outer listing of the same value.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/229/">229</link>]:
- If you set <literal>incompatible_improvements</literal> to
- 2.3.21 (or higher), unclosed comments (<literal><#--
- <replaceable>...</replaceable></literal>) and
- <literal>#noparse</literal>-s won't be silently closed at the
- end of template anymore, but cause a parsing error
- instead.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Added new <literal>Configuration</literal> constructor,
- <literal>Configuration(Version
- incompatibleImprovements)</literal>. This deprecates the vague
- <literal>Configuration()</literal> constructor, and makes using
- <literal>setIncompatibleImprovements(Version)</literal> needless
- in most cases. See an example <link
- linkend="pgui_quickstart_createconfiguration">here...</link></para>
- </listitem>
-
- <listitem>
- <para>When setting the
- <literal>incompatible_improvements</literal> setting (like with
- the constructor above) to 2.3.21, two setting defaults
- change:</para>
-
- <itemizedlist>
- <listitem>
- <para>The default of the <literal>object_wrapper</literal>
- setting
- (<literal>Configuration.getObjectWrapper()</literal>)
- changes from
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> to another
- almost identical <literal>DefaultObjectWrapper</literal>
- singleton, returned by <literal>new
- DefaultObjectWrapperBuilder(Version).build()</literal>. The
- new default object wrapper's <quote>incompatible
- improvements</quote> version is set to the same as of the
- <literal>Configuration</literal>. (See later regarding the
- 2.3.21 <quote>incompatible improvements</quote> of
- <literal>BeansWrapper</literal> and
- <literal>DefaultObjectWrapper</literal>). Furthermore, the
- new default object wrapper doesn't allow changing its
- settings; setter methods will throw
- <literal>IllegalStateException</literal>. (If anything tries
- to call setters on the old default in your application,
- that's a dangerous bug that won't remain hidden now. As the
- old default is a singleton too, potentially shared by
- independently developed components, most of them expects the
- out-of-the-box behavior from it (and the others are
- necessarily buggy). Also, then concurrency glitches can
- occur (and even pollute the class introspection cache)
- because the singleton is modified after publishing.)</para>
- </listitem>
-
- <listitem>
- <para>The default of the <literal>template_loader</literal>
- setting
- (<literal>Configuration.getTemplateLoader()</literal>})
- changes to <literal>null</literal>, which means that
- FreeMarker will not find any templates. Earlier the default
- was a <literal>FileTemplateLoader</literal> that used the
- current directory as the root. This was dangerous and
- fragile as you usually don't have good control over what the
- current directory will be. Luckily, the old default almost
- never looked for the templates at the right place anyway, so
- pretty much all applications had to set
- <literal>template_loader</literal>, so it's unlikely that
- changing the default breaks your application.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New <literal>BeansWrapper</literal>,
- <literal>DefaultObjectWrapper</literal> and
- <literal>SimpleObjectWrapper</literal> constructor that takes a
- <literal>Version</literal>
- <literal>incompatibleImprovements</literal> argument. This has
- the same role as the
- <literal>incompatible_improvements</literal> setting of the
- <literal>Configuration</literal>, but it applies to the
- <literal>ObjectWrapper</literal> instead. (As
- <literal>ObjectWrapper</literal>-s are often shared among
- multiple <literal>Configuration</literal>-s, so they can't use
- that setting of the <literal>Configuration</literal>.) In new or
- actively developed projects it's recommended to use
- <literal>Configuration.VERSION_2_3_21</literal> now. The
- constructor without the <literal>Version</literal> parameter is
- now deprecated.</para>
- </listitem>
-
- <listitem>
- <para>Safer and more memory-efficient way of managing singletons
- of <literal>DefaultObjectWrapper</literal>-s and
- <literal>BeansWrapper</literal>-s that are possibly shared by
- independently developed subsystems:</para>
-
- <itemizedlist>
- <listitem>
- <para>Instead of <literal>new
- DefaultObjectWrapper(<replaceable>...</replaceable>)</literal>
- and <literal>new
- BeansWrapper(<replaceable>...</replaceable>)</literal>, from
- now on you should use <literal>new
- DefaultObjectWrapperBuilder(version).build()</literal> and
- <literal>new BeansWrapperBuilder(version).build()</literal>.
- (The builder objects have properties (configuration
- settings) like <literal>BeansWrapper</literal> has, which
- specify the properties of the objects created.) The created
- objects are <emphasis>singletons</emphasis> (VM-wide, or at
- least Web-Application-wide) and read-only (means,
- non-configurable, hence safe to share). The main benefit of
- using these factories instead of creating new instances is
- that it allows FreeMarker to share the class introspection
- caches (an internal part of
- <literal>BeansWrapper</literal>-s/<literal>DefaultObjectWrapper</literal>-s
- that is expensive to populate) among the returned instances.
- This allow sharing the caches (and the object wrappers)
- between components that aren't aware of each other and use
- FreeMarker internally.</para>
- </listitem>
-
- <listitem>
- <para>Deprecated the static fields
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal>,
- <literal>BEANS_WRAPPER</literal> and
- <literal>SIMPLE_WRAPPER</literal>, because these
- <literal>ObjectWrapper</literal>-s are configurable (not
- read-only), and thus dangerous to use as singletons (a badly
- behaving 3rd party component can mess them up). Use the
- factories described above instead. They are also more
- flexible, as you can specify the desired
- incompatible-improvements version for them and various other
- <literal>BeansWrapper</literal> settings.</para>
- </listitem>
-
- <listitem>
- <para>Deprecated all <literal>SimpleHash</literal>,
- <literal>SimpleCollection</literal> and
- <literal>SimpleSequence</literal> constructors that didn't
- take an <literal>ObjectWrapper</literal> argument, as they
- have usually used
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> as the
- default, which itself is deprecated.</para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal>,
- <literal>DefaultObjectWrapper</literal> and
- <literal>SimpleObjectWrapper</literal> now implements the
- <literal>freemarker.template.utility.WriteProtectable</literal>
- interface with which the configuration properties of the
- object wrapper can be set permanently to read-only by
- calling <literal>writeProtect()</literal>. An attempt to
- call a setter on a such <literal>ObjectWrapper</literal>
- will immediately cause
- <literal>IllegalStateException</literal>. (This is what's
- used for the singletons returned by the
- <literal>getInstance</literal> methods too; see
- earlier).</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>The value of the <literal>time_zone</literal> setting,
- when you specify it with a <literal>String</literal> (in a
- <literal>java.util.Properties</literal> object, or via
- <literal><#setting
- <replaceable>...</replaceable>></literal>) can now be
- <literal>"JVM default"</literal> to use the JVM default time
- zone. The JVM default is the default value of that setting
- anyway, but now you can state this explicitly, or restore this
- value if it was overridden earlier.</para>
- </listitem>
-
- <listitem>
- <para>Added new configuration setting,
- <literal>sql_date_and_time_time_zone</literal>
- (<literal>Configurable.setSQLDateAndTimeTimeZone(TimeZone)</literal>).
- When this is set to non-<literal>null</literal>, the time zone
- used when dealing with <literal>java.sql.Date</literal> and
- <literal>java.sql.Time</literal> values will be this time zone
- instead of the value of the <literal>time_zone</literal>
- FreeMarker configuration setting. This is useful because JDBC
- will, usually, construct the Java <literal>Date</literal>
- objects so that they will show the year-month-day and
- hour-minute-seconds values from the database <quote>as
- is</quote> if you render them using the JVM default time zone.
- As time zone conversions for SQL date-only and SQL time-only
- values doesn't make much sense (unlike for SQL timestamps), you
- should certainly set this setting to the JVM default time zone
- (<literal>TimeZone.getDefault()</literal>, or if you configure
- FreeMarker via <literal>java.util.Properties</literal>, as
- property value "JVM default"). The default value is
- <literal>null</literal> for backward compatibility. For more
- details see the JavaDoc of
- <literal>Configurable.setSQLDateAndTimeTimeZone(TimeZone)</literal>.</para>
- </listitem>
-
- <listitem>
- <para>When configuring FreeMarker with
- <literal>java.util.Properties</literal> (typically, when the
- configuration is stored in a <literal>.properties</literal>
- file), for the settings where you could specify a fully
- qualified class name (most notably for the
- <literal>object_wrapper</literal> setting) now you can also
- specify constructor arguments and JavaBean property assignments.
- For example, now you can write
- <literal>object_wrapper=com.example.MyObjectWrapper(1, 2,
- exposeFields=true, cacheSize=5000)</literal>that's nearly
- equivalent with this Java code: <literal>obj = new
- com.example.MyObjectWrapper(1, 2); obj.setExposeFields(true);
- obj.setCacheSize(5000); object_wrapper = obj;</literal>. If you
- are using this new syntax (i.e., if you have parentheses after
- the class name, even if they are empty), and there's a builder
- class for the requested class, that will be automatically used.
- For example,
- <literal>object_wrapper=DefaultObjectWrapper(2.3.21)</literal>
- will create a <literal>DefaultObjectWrapperBuilder</literal> to
- build the final instance, thus the object wrapper will be a
- singleton instead of a new instance. The new syntax will also
- look for a public static <literal>INSTANCE</literal> field if
- there are 0 arguments and property assignments. For more details
- see the Java API documentation of
- <literal>Configuration.setSetting</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Template not found exceptions now explain that the
- template path is interpreted by a template loader, and show the
- <literal>toString</literal> of the
- <literal>TemplateLoader</literal>. The out-of-the-box
- <literal>TemplateLoader</literal> implementations now have an
- overridden <literal>toString</literal> to show the actual base
- directory and such details. Custom
- <literal>TemplateLoader</literal> implementations are encouraged
- to override <literal>toString</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>Configuration.setSharedVariables(Map/*<String,
- Object>*/)</literal> for setting the shared variables from
- Spring IoC and other IoC solutions. The already existing
- <literal>Configuration.setSharedVariable(String,
- Object)</literal> isn't a JavaBean property so it was hard to
- use for that. Furthermore, the order in which
- <literal>Configuration.setObjectWrapper</literal> and
- <literal>Configuration.setSharedVariables</literal> are called
- doesn't mater (unlike in the case of
- <literal>Configuration.setSharedVariable</literal>), which is
- essential in most IoC solutions.</para>
- </listitem>
-
- <listitem>
- <para>Mostly concerning tool (like IDE plugin) authors:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>ParseException</literal>-s now also store the
- end-location of the error, not just its start-location. This
- is useful if you want to underline the error in the source
- code, not just point at it.</para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration.getSupportedBuiltInDirectiveNames()</literal>
- can be used to return the names of directives supported by
- FreeMarker.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateExceptions</literal> now expose the
- position of the error (template name, line, column, end
- line, end column) similarly to
- <literal>ParseException</literal>-s. Where applicable, they
- also expose the blamed expression in its canonical FTL
- source form; this is mostly useful for
- <literal>InvalidReferenceException</literal>-s.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>The concurrent performance of overloaded method lookups
- (from the cache) was improved under Java 5 and later.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>Version</literal> instances that are
- <quote>incompatible improvements</quote> break points are now
- available via constants like:
- <literal>Configuration.VERSION_2_3_21</literal>.</para>
- </listitem>
-
- <listitem>
- <para>From now on, if you try to set the <quote>incompatible
- improvements</quote> to greater than the current FreeMarker
- version, or less than 2.3.0, an
- <literal>IllegalArgumentException</literal> will be thrown.
- Thus, <literal>new
- Configuration(<replaceable>someVersion</replaceable>)</literal>
- not only activates the fixes up to that version, but ensures
- that the application will not run in an environment with an
- older FreeMarker version. (On an older FreeMarker version the
- improvements that you have requested aren't implemented yet, so
- you should get an exception.)</para>
- </listitem>
-
- <listitem>
- <para>Added new configuration setting,
- <literal>show_error_tips</literal>, defaults to
- <literal>true</literal>. Sets if tips should be shown in error
- messages of errors arising during template processing.</para>
- </listitem>
-
- <listitem>
- <para>Instead of overriding
- <literal>BeansWrapper.finetuneMethodAppearance</literal> (now
- deprecated), now you can use
- <literal>BeansWrapper.setMethodAppearanceFineTuner(MethodAppearanceFineTuner)</literal>,
- so you don't need to extend the object wrapper class to
- customize this aspect.</para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>Configuration.getCoreDirecticeNames()</literal> which
- returns the names of all directives that are provided by
- FreeMarker. This can useful for IDE-s.</para>
- </listitem>
-
- <listitem>
- <para><literal>template_loader</literal> was added as possible
- configuration setting <literal>Properties</literal> key.</para>
- </listitem>
-
- <listitem>
- <para>The standard <literal>CacheStorage</literal>
- implementations now have a <literal>getSize()</literal> method
- for monitoring the cache size.
- <literal>MruCacheStorage</literal> also has
- <literal>getSoftSize()</literal> and
- <literal>getStrongSize()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Various smaller improvements in configuration setting
- errors messages.</para>
- </listitem>
-
- <listitem>
- <para>With incompatible improvements 2.3.21 only: Empty ranges
- return <literal>Constants.EMPTY_SEQUENCE</literal> instead of an
- empty <literal>SimpleSequence</literal>. This is in theory
- backward compatible, as the API only promises to give something
- that implements <literal>TemplateSequenceModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>FreeMarker now requires Java version has changed from 1.2
- to 1.4.</para>
- </listitem>
-
- <listitem>
- <para>Bugs fixed and improvements in overloaded method
- selection/invocation, but only if you create the
- <literal>BeansWrapper</literal>/<literal>DefaultObjectWrapper</literal>
- with constructor parameter
- <literal>Configuration.VERSION_2_3_21</literal> (or if you are
- using <literal>Properties</literal> to configure FreeMarker, you
- can do that like
- <literal>object_wrapper=BeansWrapper(2.3.21)</literal>), or if
- you have a <literal>Configuration</literal> with similar
- <literal>incompatible_improvements</literal> 2.3.21
- <emphasis>and</emphasis> you leave the
- <literal>object_wrapper</literal> setting on its default value.
- There's a little chance that because of these changes, a
- different overloaded method will be chosen than before, or even
- that ambiguity errors will arise where earlier they didn't
- (although the opposite is far more frequent), hence the fixes
- aren't automatically activated. But the fix mostly only effect
- calls that were failing or have chosen then wrong method
- earlier, so it's recommended to activate it for projects that
- are still actively developed. This fix includes numerous
- changes:</para>
-
- <itemizedlist>
- <listitem>
- <para>Earlier, <literal>null</literal> argument values has
- only matched overloaded methods where the corresponding
- parameter had <literal>Object</literal> type, not a subclass
- of it. That's clearly a bug. Now it considers all overloads
- where the parameter type is non-primitive, and just like the
- Java language, it choses the one with the most specific type
- among them. This is the most important fix, and also the
- most risky one regarding backward-compatibility. Like if you
- have <literal>m(Object o)</literal> and <literal>m(String
- s)</literal> in a Java class, earlier for a
- <literal>m(null)</literal> call in the template it has
- chosen <literal>m(Object o)</literal>, but now it will
- choose <literal>m(String s)</literal> instead (because
- <literal>String</literal> is also
- <literal>null</literal>-able and is more specific than
- <literal>Object</literal>). Furthermore, if you also had
- <literal>m(File f)</literal> in the same class, now it will
- cause an ambiguity exception, since the specificity of
- <literal>File</literal> and <literal>String</literal> can't
- be compared (same rule as under Java language), while
- earlier that wasn't a problem as only <literal>m(Object
- o)</literal> was seen as applicable.</para>
- </listitem>
-
- <listitem>
- <para>The behavior of numbers with overloaded method
- selection was heavily reworked:</para>
-
- <itemizedlist>
- <listitem>
- <para>If possible, it now always choses the overload
- where overflow and truncation to integer (like 1.5 to 1)
- is avoided. Among the methods where no such critical
- loss occurs, it choses the overload with the least risk
- of precision loss (unless other conditions with higher
- priority suggest otherwise). Earlier, the method
- selection was prone to do choices that led to overflow
- or precision loss, especially when the parameter was a
- literal with decimals.</para>
- </listitem>
-
- <listitem>
- <para>Overloaded method call can now convert to
- non-primitive numerical types, like a
- <literal>Byte</literal> or <literal>byte</literal> value
- is automatically converted to <literal>Integer</literal>
- if the parameter type is <literal>Integer</literal>.
- (This has always worked for non-overloaded methods.)
- Earlier where such conversion was needed, the method
- wasn't seen seen applicable.</para>
- </listitem>
-
- <listitem>
- <para>Method selection is now not only based on the type
- of the wrapped number, but also on its value. For
- example, a <literal>Long</literal> with value
- <literal>1</literal> is now seen as compatible with a
- method with parameter type <literal>int</literal> or
- <literal>short</literal> or <literal>byte</literal>, as
- <literal>1</literal> can be stored in those without
- loss. This is important as unlike in Java language, in
- FTL you doesn't have strict control over the numerical
- types (the type of the wrapped number, actually), as FTL
- has no type declarations. (If multiple compatible
- methods are available, it will still try to chose the
- one with the same or bigger numerical type.)</para>
- </listitem>
-
- <listitem>
- <para>Conversion from/to <literal>BigInteger</literal>
- is now supported.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Method choice ambiguity errors now occur much less
- often. Ambiguities was and are resolved by selecting the
- compatible methods then choosing the one with the most
- specific parameter types among them. The changes are:</para>
-
- <itemizedlist>
- <listitem>
- <para>When comparing the overall specificity of two
- parameter lists: Earlier the parameter list seen as more
- specific was the one that had some parameters that won
- in specificity, and if both had such parameters then it
- was an ambiguity. Now it's enough if a method has more
- such parameters where it's a better match than the other
- has, or if the two methods are still equal, if it has
- the first better matching parameter. This can lead to
- choices that seem arbitrary (but are still
- deterministic), but as there's no automated way of
- discovering method selection ambiguities in templates
- (unlike in Java source code, where they will be detected
- during compilation), especially as overloaded selection
- has to rely on the <emphasis>runtime</emphasis> type of
- the values which even make proper testing hard, this was
- considered to be a better compromise than throwing an
- exception whenever the choice of the method is not
- obvious. Also note that in fact this mechanism is more
- complicated than just counting the <quote>winner</quote>
- parameter positions for each methods, as certain kind of
- wins are stronger than any number of the others: wins
- where the other possibility is risking of substantial
- mantissa precision loss are the strongest (like dropping
- decimals versus not to), wins where the primitive type
- wins over the boxed class is the weakest (like
- <literal>int</literal> versus
- <literal>Integer</literal>), subclassing wins (like
- <literal>String</literal> versus
- <literal>Object</literal>) are between these two.</para>
- </listitem>
-
- <listitem>
- <para>When comparing the specificity of two parameters
- types at the same parameter position: The algorithm now
- considers a primitive type as more specific that its
- corresponding boxing class (like <literal>int</literal>
- is considered to be more specific than
- <literal>Integer</literal>).</para>
- </listitem>
-
- <listitem>
- <para>There was a bug with overloaded varargs methods of
- different parameter counts, where sometimes the last
- parameters of the compared methods was ignored, which is
- taking away a potential deciding factor and thus can
- lead to ambiguity error. Whether this happened depends
- on the order in which the Java reflection API has
- returned the methods, which is undocumented and known to
- change at least after some Java updates, breaking the
- application.</para>
- </listitem>
-
- <listitem>
- <para>When comparing the specificity of two array types,
- until now they were seen as equal. Now the component
- types are compared, and then that with the less specific
- component type is preferred. For example, among
- <literal>f(String[])</literal> and
- <literal>f(Object[])</literal>, the last will always
- win. This might sounds controversial, but as we can't
- efficiently tell the common type of all the items in a
- sequence or <literal>List</literal>, an so we don't know
- if both arrays are indeed valid targets, we go for the
- safest choice.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>FTL sequence values (like Java
- <literal>List</literal>-s or FTL
- <literal>[<replaceable>x</replaceable>,
- <replaceable>y</replaceable>,
- <replaceable>...</replaceable>]</literal> constants) were
- not seen as applicable to a parameter with array type if
- there were multiple overloaded methods with the same number
- of parameters but with different types on the position of
- the array parameter. That is, if you had
- <literal>f(String[])</literal> and
- <literal>f(String)</literal> in Java, then
- <literal>f(['foo', 'bar'])</literal> in the template
- reported no compatible overloads. Now it will choose
- <literal>f(String[])</literal>. Note that if there's also an
- <literal>f(List)</literal> or even an
- <literal>f(Collection)</literal>, it will prefer those over
- arrays. (For consistency, this conversion will work even if
- the argument is a <literal>List</literal> that come directly
- from Java (as opposed to be created inside FTL), i.e., when
- it was wrapped then unwrapped to the original
- <literal>List</literal> object.) For a multidimensional
- array parameter type, this conversion works recursively, so
- you can pass in a sequence-of-sequences as the
- argument.</para>
- </listitem>
-
- <listitem>
- <para>FTL sequence values that wrapped a Java array (when
- FreeMarker was also aware of that via
- <literal>AdapterTemplateModel</literal> or
- <literal>WrapperTemplateModel</literal>) were not seen as
- applicable to a parameter with <literal>List</literal> type
- if there were multiple overloaded methods with the same
- number of parameters but with different types on the
- position of the array parameter. So this is pretty much like
- the issue described in the previous point, but for array to
- <literal>List</literal> conversion. The array to
- <literal>List</literal> conversion will be avoided if
- possible, but it will be attempted if there's no other
- alternative. Note that unlike with <literal>List</literal>
- to array conversions, here FreeMarker can't cope with
- multi-dimensional lists, that is, an array-of-arrays won't
- be converted to a
- <literal>List</literal>-of-<literal>List</literal>-s.</para>
- </listitem>
-
- <listitem>
- <para>FTL string to Java
- <literal>char</literal>/<literal>Character</literal>
- conversion now works for overloaded method parameters;
- earlier it has worked for non-overloaded methods only. If
- the string length is 1, it will be seen as compatible with
- parameters with <literal>char</literal> or
- <literal>Character</literal> type.</para>
- </listitem>
-
- <listitem>
- <para>Decreased the chance of choosing the wrong target Java
- type when unwrapping multi-typed FTL values: When unwrapping
- a parameter value that implements multiple FTL types (e.g.
- string and hash) but doesn't implement
- <literal>AdapterTemplateModel</literal> or
- <literal>WrapperTemplateModel</literal>, a decision has to
- be made if to what Java type the value should be unwrapped
- to (e.g. to <literal>String</literal> or to
- <literal>Map</literal>). In earlier versions that decision
- was made based on the most specific common super type of the
- parameters types of the given parameter position. However,
- it's quite common that the common super type is too generic,
- usually <literal>Object</literal>. Now
- <literal>BeansWrapper</literal> stores <quote>type
- flags</quote> for each parameter position of overloaded
- methods, from which it can tell whether a potential target
- Java type occurs in any of the overloads on the given
- parameter position.</para>
- </listitem>
-
- <listitem>
- <para>In many cases, less specific hint class was used for
- unwrapping than that was possible within the limitations of
- the applied hint generation algorithm. (The unwrapping hint
- has influence when there's an ambiguity regarding how to
- create a Java object form an FTL value. In the vast majority
- of the cases, a too generic hint has no effect.) (This is a
- highly technical topic. The way it works is that a single
- common unwrapping hint class is chosen for a given argument
- position shared by the overloads that has the same number of
- parameters, and that hint class has to be as specific as
- possible while it must fit all those parameter types. The
- issue with the too generic hints had several instances: (a)
- When the most specific common class/interface of two
- same-position parameter types was searched, if there was
- multiple common classes/interfaces that had no relationship
- (this is always at most one class and one or more unrelated
- interfaces), due to the ambiguity it has felt back to using
- <literal>Object</literal> as the unwrapping hint. Now if
- there's a non-<literal>Object</literal> class among them in
- such case, it will be chosen as the hint (i.e., we ignore
- the common interfaces). Otherwise if only a single interface
- remains by removing <literal>Cloneable</literal>,
- <literal>Serializable</literal>, and
- <literal>Comparable</literal> (in that order), that will be
- chosen. Only then it falls back to
- <literal>Object</literal>. (b) The common most specific
- class of a primitive type and the corresponding boxing class
- was sometimes <literal>Object</literal> instead of the
- boxing class. This has depended on Java's internal ordering
- of the methods, and so were quite unpredictable, like the
- result could change after upgrading Java under the
- application. (c) The common superclass of a numerical
- primitive value and a numerical non-primitive value was
- always <literal>Object</literal>, now if they are a
- primitive-boxing class pair, then it's the boxing class,
- otherwise it's <literal>Number</literal>. (d) If the varags
- parameter position was not the same in all the overloaded
- varargs methods, sometimes some varargs arguments where
- unwrapped with too generic hints. When this happened was
- unpredictable as it depended on Java's internal method
- ordering again.)</para>
- </listitem>
-
- <listitem>
- <para>When unwrapping method call arguments before calling a
- Java method, if the argument was an
- <literal>AdapterTemplateModel</literal> and the target
- parameter type was primitive,
- <literal>AdapterTemplateModel.getAdaptedObject(Class
- hint)</literal> has received the primitive type of the
- target parameter (like <literal>int</literal> instead of
- <literal>Integer</literal>) as the hint. This did not make
- sense since <literal>getAdaptedObject</literal> can only
- return <literal>Object</literal>-s, not primitive values.
- Yet, <literal>BeansWrapper</literal> has expected the
- returned value to be of the primitive type, otherwise it has
- discarded it. Exactly the same problem occurs with
- <literal>WrapperTemplateModel</literal>. Thus, ultimately,
- if the target parameter type was primitive and some of these
- interfaces were implemented, their return value was always
- discarded and FreeMarker has felt back to other means of
- unwrapping. Now <literal>BeansWrapper</literal> always
- passes in and expects the boxing type (like
- <literal>Integer</literal>) instead of the primitive
- type.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/373/">373</link>]:
- These are three bugs actually, which can cause problems when
- FreeMarker re-loads a template because of a charset override
- with <literal><#ftl encoding="..."></literal>: First, if
- the template loader was a <literal>URLTemplateLoader</literal>,
- when <literal>TemplateLoader.getReader()</literal> was called
- for the second time, and the <literal>Reader</literal> returned
- for the first time was already used, it might returned an empty
- or corrupted template, depending on the backing URL
- implementation. Secondly, when FreeMarer has decided if a
- template file has to be re-loaded because its encoding specified
- with <literal><#ftl encoding="..."></literal> differs from
- the encoding used for loading the template first, it has used
- case-sensitive comparison, thus often re-loaded needlessly (like
- "UTF-8" and "utf-8" mean the same). Now this comparison is
- case-insensitive. Last not least, when retrying with the second
- charset, the <literal>TemplateCache</literal> has forgotten to
- close the first <literal>Reader</literal>, which can be a handle
- leak.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/411/">411</link>]:
- Invalid and redundant execution environment names from the OSGi
- bundle manifest were removed. It looks like this now:
- <literal>Bundle-RequiredExecutionEnvironment: J2SE-1.5,
- J2SE-1.4</literal>. That is, we prefer (and compile against)
- 1.5, but only require 1.4.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/409/">409</link>]:
- <literal>SimpleHash</literal>'s internal <literal>Map</literal>
- is concurrently modified on a non-safe way when getting length-1
- <literal>String</literal> key that exists but maps to
- <literal>null</literal>. This operation will accidentally add a
- <literal>Character</literal> key to the internal map, which is
- not a thread-safe operation.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/273/">273</link>]:
- <literal>TemplateLoader</literal>-s that use
- <literal>java.net.URLConnection</literal>-s should set
- <literal>URLConnection.useCaches</literal> to
- <literal>false</literal>, or else it won't detect template
- caches on certain configurations.</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>URLTemplateLoader</literal> and its
- subclasses and
- <literal>WebApplicationTemplateLoader</literal> now has a
- <literal>setURLConnectionUsesCaches(Boolean)</literal>
- method. It's recommended to set this property to
- <literal>false</literal> from its default backward
- compatible value, <literal>null</literal>. As FreeMarker has
- its own template cache with its own update delay setting
- (<literal>template_update_delay</literal>,
- <literal>Configuration.setTemplateUpdateDelay(int)</literal>),
- it shouldn't cause performance problems. The
- <literal>null</literal> value will leave the caching of the
- <literal>java.net.URLConnection</literal> on its default,
- which is usually <literal>true</literal>.</para>
- </listitem>
-
- <listitem>
- <para>If <literal>incompatible_improvements</literal> is set
- to 2.3.21 (or higher) and templates are loaded through
- <literal>Configuration.getTemplate</literal>, and the
- <literal>TemplateLoader</literal> in use has
- <literal>URLConnectionUsesCaches</literal> left on
- <literal>null</literal>, it will behave as if it was set to
- <literal>false</literal>. Note that this
- <literal>incompatible_improvements</literal> trick only
- works if the template is loaded through
- <literal>Configuration.getTemplate</literal> (or
- <literal>TemplateCache</literal>).</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug fixed: When changing the properties of
- <literal>DefaultObjectWrapper</literal> or
- <literal>BeansWrapper</literal> that influenced class
- introspection results (like <literal>exposureLevel</literal> or
- <literal>exposeFields</literal>), the introspection cache wasn't
- cleared, and thus returned stale information for the classes
- that were introspected before those properties were changed. Now
- changing such properties always clears the introspection
- caches.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Constants used for empty sequence, empty hash,
- empty collection and empty iterator weren't
- <literal>Serializable</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/311/">300</link>]:
- Logger class availability check was incorrect in that it has
- checked the availability of logger libraries with the thread
- context class loader, then later it tried to link to them with
- the defining class loader of the FreeMarker classes. With some
- class loader setups (notably under Netbeans sometimes) this led
- to <literal>ClassDefNotFoundError</literal>-s that made
- FreeMarker unusable. (The check itself was also not very
- durable, as it didn't expected <literal>LinakeError</literal>-s,
- only <literal>ClassNotFoundException</literal>.)</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <literal>ClassUtil.forName</literal>, used
- inside FreeMarker everywhere to resolve class names to classes,
- if the thread context class loader is <literal>null</literal>,
- no longer tries to load with the bootstrap class loader before
- loading with the defining class loader of FreeMarker.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="http://sourceforge.net/p/freemarker/bugs/311/">311</link>]:
- <literal>TemplateBooleanModel.TRUE</literal> and
- <literal>FALSE</literal> are now serializable</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Various issues in <literal>Version</literal>
- class, such as wrong <literal>hashCode</literal>, possible
- concurrency glitches, and acceptance of some malformed version
- strings.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/414/">414</link>]:
- Eclipse debug mode running was suspended during FreeMarker
- static initialization on the JRebel availability checked if
- JRebel wasn't available.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The license has changed from our proprietary BSD-Style
- license to the well know "Apache License, Version 2.0".
- Furthermore, the copyright owner has changed from "Visigoth
- Software Society" (which was founded by Jonathan Revusky) to the
- three main FreeMarker 2 developers/contributors, "Attila
- Szegedi, Daniel Dekany, and Jonathan Revusky". See the <link
- linkend="app_license">new license here</link>.</para>
- </listitem>
-
- <listitem>
- <para>The required minimum Java version was raised from 1.2 to
- 1.4. FreeMarker will not work on Java 1.2 or 1.3.</para>
- </listitem>
-
- <listitem>
- <para>Many smaller improvements and fixes in the Manual and API
- JavaDocs.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_20">
- <title>2.3.20</title>
-
- <para>Date of release: 2013-06-27</para>
-
- <para>If you are IDE/tools author, <link
- linkend="version_2_3_20_ide">note these changes</link>.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Error message quality improvements:</para>
-
- <itemizedlist>
- <listitem>
- <para>Many error messages are now more helpful, especially
- for users who are not experienced with FreeMarker. For
- example, some of the most common user mistakes are
- recognized and tips are shown to fix them, reducing support
- costs or the time employees spend to figure them out.</para>
- </listitem>
-
- <listitem>
- <para>It's now ensured that the error location in the
- template is included in the message returned by
- <literal>TemplateException.getMessage()</literal>. The stack
- trace always showed this information anyway, but some users
- only see the <quote>message</quote>, not the stack trace,
- and that often didn't contained the location.</para>
- </listitem>
-
- <listitem>
- <para>The template language part of the stack trace is now
- more detailed, and easier to understand. This is especially
- helpful in applications that use a complex library of macros
- and functions.</para>
- </listitem>
-
- <listitem>
- <para>Several smaller bugs were fixed that made the error
- information wrong or lacking.</para>
- </listitem>
-
- <listitem>
- <para>The layout of the error messages is now more
- consistent, and generally easier to read.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Changes regarding boolean to string conversions and
- formatting:</para>
-
- <itemizedlist>
- <listitem>
- <para><literal>?c</literal> (computer-language formatting)
- now works with booleans too, always giving
- <literal>"true"</literal> or <literal>"false"</literal>
- regardless of the <literal>boolean_format</literal>. This
- way it's safe for generating JavaScript in a context where
- human-readable text is also rendered.</para>
- </listitem>
-
- <listitem>
- <para>If the <literal>boolean_format</literal> setting is
- set to anything but the default
- <literal>"true,false"</literal> value, boolean values will
- be automatically converted to string where a string value is
- expected by the template language, instead of giving an
- error. This helps you spare
- those<literal>?string</literal>-s after boolean values. This
- is the same logic as with numbers and dates, which were
- always automatically converted to string, according the
- corresponding format setting. Except, the provided default
- boolean format is useless for automatic conversion (but it's
- still there for <literal>?string</literal>, for backward
- compatibility), hence it must be set manually. (We certainly
- couldn't come up with a sensible default anyway, as for
- booleans it depends too much on the application, not to
- mention the localisation issues.)</para>
-
- <para>Exactly like with numbers and dates, automatic
- conversion doesn't happen in these cases:</para>
-
- <itemizedlist>
- <listitem>
- <para>Comparisons, i.e., <literal>someBoolean ==
- 'true'</literal> is still an error</para>
- </listitem>
-
- <listitem>
- <para>Method calls where the declared type of the
- parameter is <literal>String</literal> but the actual
- value is a boolean; still an error</para>
- </listitem>
-
- <listitem>
- <para>When the boolean value is used as key in
- <literal><replaceable>expr</replaceable>[<replaceable>key</replaceable>]</literal>,
- it's still an error (there was no automatic conversion
- there for other types either, as numerical and string
- keys have different meaning)</para>
- </listitem>
-
- <listitem>
- <para>The opposite direction, i.e., string to boolean
- conversion; won't happen</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New built-ins for numbers: <link
- linkend="ref_builtin_abs"><literal>abs</literal></link>, <link
- linkend="ref_builtin_is_nan"><literal>is_nan</literal></link>,
- <link
- linkend="ref_builtin_is_infinite"><literal>is_infinite</literal></link>.
- Like <literal>n?abs</literal> will give the absolute value of
- <literal>n</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New built-in for sequences: <link
- linkend="ref_builtin_join"><literal>join</literal></link>. Like
- <literal>[1, 2, 3]?join(", ")</literal> will give the string
- <literal>"1, 2, 3"</literal>.</para>
- </listitem>
-
- <listitem>
- <para>If you set the
- <literal>incompatible_improvements</literal> setting (see <link
- xlink:href="https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setIncompatibleImprovements%28freemarker.core.Version%29">here</link>)
- to <literal>2.3.20</literal> or higher, <literal>?html</literal>
- will escape apostrophe-quotes just like
- <literal>?xhtml</literal> does. Utilizing this is highly
- recommended, because otherwise if interpolations are used inside
- attribute values that use apostrophe-quotation (<literal><foo
- bar='${val}'></literal>) instead of plain quotation mark
- (<literal><foo bar="${val}"></literal>), they might
- produce HTML/XML that's not well-formed. Note that
- <literal>?html</literal> didn't do this because long ago there
- was no cross-browser way of doing this, but it's not a real
- concern anymore. Also note that this will be the default
- behavior starting from 2.4.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/390/">390</link>]
- (and other improvements): <literal>?js_string</literal> and
- <literal>?json_string</literal> didn't escape the
- <literal>u2028</literal>-<literal>u2029</literal> line
- terminators (problem for JavaScript) and the
- <literal>u007F</literal>-<literal>u009F</literal> control
- characters (maybe a problem in JSON, depending on
- implementation). Furthermore, the escaping of
- <literal>\</literal>, <literal><</literal>, and
- <literal>></literal> become safer in that now they are
- escaped whenever it can't be guaranteed that they won't be part
- of <literal><!</literal>, <literal>]]></literal> or
- <literal></</literal>. Earlier they were only escaped when it
- was known that they are part of these patterns, thus it was
- possible to assemble these patterns from two adjacent
- interpolations. Additionally, from now on
- <literal><?</literal> and <literal>--></literal> also
- count as dangerous patterns, and will trigger
- <literal><</literal> and <literal>></literal>
- escaping.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: The following string built-ins didn't coerce
- the numerical, date (and now the boolean) left-values to string,
- instead they threw a type error: contains,
- <literal>index_of</literal>, <literal>last_index_of</literal>,
- <literal>left_pad</literal>, <literal>right_pad</literal>,
- <literal>matches</literal>, <literal>replace</literal>,
- <literal>split</literal>, <literal>new</literal>. The other
- string built-ins already did this conversion for a long time;
- this was an accidental inconsistency.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: With the default arithmetic engine, it's now
- supported to compare infinite (positive or negative) with 0, to
- decide its sign.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>BeansWrapper</literal> introspection cache
- improvements:</para>
-
- <itemizedlist>
- <listitem>
- <para>Added public API to <literal>BeansWrapper</literal>
- for clearing the class cache:
- <literal>clearClassIntrospecitonCache()</literal>,
- <literal>removeFromClassIntrospectionCache(Class)</literal></para>
- </listitem>
-
- <listitem>
- <para>Significantly improved multi-core performance:</para>
-
- <itemizedlist>
- <listitem>
- <para>Uses <literal>ConcurrentHashMap</literal> when
- running on Java 5 or later.</para>
- </listitem>
-
- <listitem>
- <para>The cache won't block readers while introspecting
- a class after a cache miss</para>
- </listitem>
-
- <listitem>
- <para>If multiple threads need to introspect the same
- class that's not in the cache yet, only one of them will
- do it, the others will wait for its results.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/361/">361</link>]:
- There was a small chance of deadlock when class-reloading
- was detected. Locking was redesigned to prevent such
- oversights in the future.</para>
- </listitem>
-
- <listitem>
- <para>The internal package-visible
- <literal>freemarker.ext.beans</literal> API was slightly
- changed as the result of internal cleanup. Nobody but the
- FreeMarker developers should define classes in that package,
- so it shouldn't break anything. But if somebody did some
- in-house hacks there, re-compile to see if it still
- works.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Nameless templates (those directly created with
- <literal>new Template(null,
- <replaceable>...</replaceable>)</literal> instead of loaded
- through <literal>Configuration</literal>) couldn't include or
- import other templates, and thrown a
- <literal>NullPointerException</literal> when they tried to. Now
- they resolve relative paths as if they were in the template root
- directory.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix: Regular expression built-ins and some logger
- libraries (most importantly Log4J) were unavailable on the
- Google App Engine platform. This fix is only present in the
- GAE-compatible build, 2.3.20-gae.</para>
- </listitem>
-
- <listitem>
- <para>Added new method to <literal>Configuration</literal>:
- <literal>CacheStorage getCacheStorage()</literal></para>
- </listitem>
-
- <listitem>
- <para>Added new methods to <literal>Environment</literal> to
- make comparisons among <literal>TemplateModel</literal>-s
- according the rules of the template language operators:
- <literal>applyEqualsOperator</literal>,
- <literal>applyEqualsOperatorLenient</literal>,
- <literal>applyLessThanOperator</literal>,
- <literal>applyLessThanOrEqualsOperator</literal>,
- <literal>applyGreaterThanOperator</literal>,
- <literal>applyWithGreaterThanOrEqualsOperator</literal></para>
- </listitem>
-
- <listitem>
- <para>Added new method,
- <literal>Environment.isInAttemptBlock()</literal> to check if we
- are within an <literal>#attempt</literal> block. This can be
- useful for <literal>TemplateExceptionHandler</literal>-s, as
- then they don't need to print the error to the output since
- <literal>#attempt</literal> will roll it back anyway. This is
- already utilized by the built-in
- <literal>TemplateExceptionHandler</literal>-s
- (<literal>DEBUG_HANDLER</literal> and
- <literal>HTML_DEBUG_HANDLER</literal>).</para>
- </listitem>
-
- <listitem>
- <para>Added convenience constructor <literal>Template(String
- name, String sourceCode, Configuration cfg)</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateException</literal>-s and
- <literal>TemplateModelExcepton</literal>-s now can have
- <literal>Throwable</literal> cause, not just
- <literal>Exception</literal> (it was an old oversight that
- somehow wasn't fixed so far).</para>
- </listitem>
-
- <listitem>
- <para>Parsing error messages under the JBoss Tools FreeMarker
- IDE now doesn't contain the usual location line, so that the
- actual error description is immediately visible in the Eclipse
- <quote>Problems</quote> view. (It's a <quote>hack</quote> in
- FreeMarler itself; it tries to detect if it runs under the
- plugin and then changes its behavior.)</para>
- </listitem>
-
- <listitem xml:id="version_2_3_20_ide">
- <para>Mostly concerning tool (like IDE plugin) authors:</para>
-
- <itemizedlist>
- <listitem>
- <para>The error message formats (what
- <literal>Throwable.getMessage()</literal> returns) were
- heavily changed for <literal>TemplateException</literal>-s,
- and somewhat for <literal>ParseException</literal>-s. It's
- unlikely that anybody depends on these, but if you tried to
- parse these messages, be aware of this.</para>
- </listitem>
-
- <listitem>
- <para>Fixed bug where <literal>ParseException</literal> has
- contained 0 as line and column number for lexical errors.
- Now it contains the correct information.</para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>ParseException.getEditorMessage()</literal>: As in
- IDE-s the error markers show the error location to the user
- already, the location should not be repeated in the error
- message. So in IDE-s you should use this method instead of
- <literal>getMessage()</literal>. (Under JBoss Tools:
- FreeMarker now <emphasis>tries</emphasis> to detect that it
- runs under the plugin, and then it already does this, except
- that it still shows the column number as that's missing from
- the error marker location.)</para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>ParseException.getTemplateName()</literal></para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>Configuration.getSupportedBuiltInNames()</literal>.
- As new built-ins
- (<literal><replaceable>expr</replaceable>?<replaceable>builtin_name</replaceable></literal>)
- are very often added to new FreeMarker versions,
- auto-completion or syntax highlighting should use this set
- instead of a fixed set of a names.</para>
- </listitem>
-
- <listitem>
- <para>The format returned by
- <literal>TemplateElement.getDescription()</literal> was
- heavily changed. It's what FTL stack traces and maybe some
- outline views (tree-views) show. It was always for human
- reading (and till now was too inconsistent for anything
- else), so it's unlikely that this breaks anything.</para>
- </listitem>
-
- <listitem>
- <para>There were some smaller changes in
- <literal>freemarker.debug</literal>, and it's expected that
- there will be more, so it was marked as experimental. As far
- as we know, nobody used it, so it shouldn't break
- anything.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>In <emphasis>experimental status only</emphasis>, but
- <literal>freemarker.jar</literal> is now an OSGi bundle (see
- <literal>freemarker.jar/META-INF/MANIFEST.MF</literal>).
- Depending on user feedback, the bundle description may change in
- the future. So please give feedback, especially if you are an
- OSGi expert!</para>
- </listitem>
-
- <listitem>
- <para>Improved the HTML generated by
- <literal>HTML_DEBUG_HANDLER</literal> (the
- red-on-yellow-background error message). Most importantly, now
- it will word-wrap. Other changes are minor, like now it can
- break out of CDATA sections, or now it has less intense
- colors.</para>
- </listitem>
-
- <listitem>
- <para>New <literal>Template</literal> method,
- <literal>getActualTagSyntax()</literal>: Tells if the template
- is using traditional or square-bracket syntax. As the syntax can
- be overridden in the template, also it's possibly decided by
- auto-detection, it wasn't trivial to it tell till now.</para>
- </listitem>
-
- <listitem>
- <para>Added some utility methods that are useful for generating
- error messages in custom directives:
- <literal>ClassUtil.getFTLTypeDescription(TemplateModel)</literal>,
- <literal>getShortClassName</literal>,
- <literal>getShortClassNameOfObject</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/364/">364</link>]:
- <literal>freemarker.template.EmptyMap</literal> (which is passed
- to <literal>TemplateDirectiveModel</literal>-s if there are no
- parameters) now allows <literal>remove(key)</literal>,
- <literal>clear()</literal> and
- <literal>putAll(anyEmptyMap)</literal> as these do nothing
- anyway.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/375/">375</link>]:
- <literal>NullPointerException</literal> on IBM J9 VM (not on the
- Sun/Oracle implementation) in <literal>BeansWrapper</literal>
- when the Java implementation legally returns
- <literal>null</literal> for some <literal>BeanInfo</literal>
- getters.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix: Cloning a <literal>Configuration</literal> didn't
- deep-clone the data structures storing the
- <literal>auto_imports</literal> and
- <literal>auto_includes</literal> settings, hence possibly
- leading to aliasing problems.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/377/">377</link>]:
- After a failed method call the exception handler could fail in
- the rare occasion when
- <literal>targetObject.toString()</literal> fails, raising a
- runtime exception that not even the <literal>attempt</literal>
- directive will catch.</para>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/391/">391</link>]:
- If a template name has contained <literal>*</literal> that was
- not the only character in the path step, it threw
- <literal>NegativeArraySizeException</literal> instead of
- <literal>FileNotFoundException</literal>.</para>
- </listitem>
-
- <listitem>
- <para>With the default arithmetic engine, performance
- optimizations of comparison operations when some of the numbers
- is 0, and when the sign of the numbers differ.</para>
- </listitem>
-
- <listitem>
- <para>Some smaller fixes in
- <literal>TemplateElement.getCanonicalForm()</literal>, also some
- quality improvements there.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixes in <literal>classic_compatible</literal> mode
- (this mode is to help migrating from FreeMarker 1), thanks to
- Information Mosaic:</para>
-
- <itemizedlist>
- <listitem>
- <para>When a macro was called with a
- <literal>null</literal>/missing parameter value, it has
- caused error like in FreeMarker 2.3</para>
- </listitem>
-
- <listitem>
- <para>When a hash literal contained reference to a
- <literal>null</literal>/missing variable, like in <literal>{
- 'a': missingVar }</literal>, it has caused an error like in
- 2.3</para>
- </listitem>
-
- <listitem>
- <para>When a sequence literal contained reference to a
- <literal>null</literal>/missing variable, like in
- <literal>[1, missingVar]</literal>, it has caused an error
- like in 2.3</para>
- </listitem>
-
- <listitem>
- <para>When a the left-side of the <literal>.</literal> (dot)
- or <literal>[<replaceable>key</replaceable>]</literal>
- operator was <literal>null</literal> or missing variable,
- like in <literal>missingVar.subVar</literal>, it has caused
- an error like in 2.3</para>
- </listitem>
-
- <listitem>
- <para>When <literal>BeanModel</literal>-s are tried to be
- treated as strings, for most subclasses it has failed with
- type error, like in 2.3. In FreeMarker 1 all
- <literal>BeanModel</literal>-s were
- <literal>TemplateScalarModel</literal>-s, so it should
- succeed. The fix for this only works where FreeMarker
- <emphasis>coerces</emphasis> to string (string built-ins on
- the left-side and concatenation (<literal>+</literal>) and
- interpolation
- (<literal>${<replaceable>...</replaceable>}</literal>) do
- that), otherwise unfortunately it will still fail.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>classic_compatible</literal> setting now
- accepts value <literal>2</literal> along
- <literal>true</literal> (alias <literal>1</literal>) and
- <literal>false</literal> (alias <literal>0</literal>).
- <literal>2</literal> means <literal>true</literal> but with
- emulating bugs in early 2.x classic-compatibility mode.
- Currently this only affects how booleans are converted to
- string; with <literal>1</literal> it's always
- <literal>"true"</literal>/<literal>""</literal>, but with
- <literal>2</literal> it's <literal>"true"/"false"</literal>
- for values wrapped by <literal>BeansWrapper</literal> as
- then <literal>Boolean.toString()</literal> prevails. Note
- that
- <literal><replaceable>someBoolean</replaceable>?string</literal>
- will always consistently format the boolean according the
- <literal>boolean_format</literal> setting, just like in
- FreeMarker 2.3.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bug fix [<link
- xlink:href="https://sourceforge.net/p/freemarker/bugs/394/">394</link>]:
- When trying to access the optional Jython (or W3C DOM) classes
- has failed in <literal>DefaultObjectWrapper</literal> with an
- <literal>Error</literal>, rather than with an
- <literal>Exception</literal>, loading the
- <literal>DefaultObjectWrapper</literal> class itself has failed,
- instead of the Jython (or W3C DOM) support being disabled. From
- now in, it will survive any kind of <literal>Throwable</literal>
- there, and if the <literal>Throwable</literal> is not an
- <literal>ClassNotFoundException</literal>, it will also log the
- <literal>Throwable</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Improved the performance of
- <literal>(thisVarIsMissing.foo)!default</literal> and similar
- <emphasis>parenthetical</emphasis> existence operators and
- existence built-ins in the case when the
- <literal>null</literal>/missing variable is not the last step
- inside the parenthesis. In that case it's about 30 times faster
- now, measured on Java 6. In other cases (when all variables
- exists, or the the last step is missing) the performance is
- about the same (relatively fast) as before.</para>
- </listitem>
-
- <listitem>
- <para>Added interface
- <literal>freemarker.cache.CacheStorageWithGetSize</literal>
- which allows querying the current number of cache entries in a
- <literal>CacheStorage</literal> that implements it. It's
- implemented by all out-of-the-box
- <literal>CacheStorage</literal> implementations. Also added
- <literal>getStrongSize()</literal> and
- <literal>getSoftSize()</literal> to
- <literal>MRUCacheStorage</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Version and build information changes:</para>
-
- <itemizedlist>
- <listitem>
- <para>The form of the nightly build version numbers has
- changed to be Maven/JSR 277 compliant. What earlier was
- <literal>2.3.19mod</literal> is now something like
- <literal>2.3.20-nightly_20130605T130506Z</literal> (note how
- the last points to the target release version instead of the
- last release version).</para>
- </listitem>
-
- <listitem>
- <para>The form of the Release Candidate and Preview version
- numbers where changed to be Maven/JSP 277 compliant:
- <literal>2.4pre2</literal> is now
- <literal>2.4.0-pre02</literal>, <literal>2.4rc1</literal> is
- now <literal>2.4.0-rc01</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Added new static method to
- <literal>Configuration</literal> to query build date:
- <literal>getBuildDate()</literal>. This is also printed by
- the main command line class.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Various internal code cleanups.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Many JavaDoc improvements, mostly in the documentation of
- the basic (most frequently used) classes.</para>
- </listitem>
-
- <listitem>
- <para>FreeMarker source code has moved to GitHub (<link
- xlink:href="???">https://github.com/freemarker/freemarker</link>),
- other project resources has remained on sourceforge.net.</para>
- </listitem>
-
- <listitem>
- <para>Project structure cleanup, Ivy-based build script.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_19">
- <title>2.3.19</title>
-
- <para>发布日期:2012-02-29</para>
-
- <para>不要忘了 <link linkend="v2319secfix">安全相关的修复</link>,
- 它可能会影响到你的应用程序!</para>
-
- <section>
- <title>FTL部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para><emphasis>注意</emphasis>:在 2.3.17 版引入的 <link
- linkend="ref_builtin_date_iso">ISO 8601 日期/时间格式内建函数</link>
- 的输出,有轻微的调整。从现在开始,时区偏移,用于显示而且不是
- <literal>Z</literal> 时,通常包含了分钟。比如在模板输出中,
- <literal>15:30:15+02</literal> 现在就变成 <literal>15:30:15+02:00</literal>
- 了。根据ISO 8601(那么ISO 8601 日期/时间格式的内容应该不会有问题)两种格式都是合法的,
- 但是最后的格式使用了 XML Schema 日期/时间格式来编译,因此做了这个修改。</para>
- </listitem>
-
- <listitem>
- <para>新的内建函数,用来转义JSON字符串:<link
- linkend="ref_builtin_json_string"><literal>json_string</literal></link>。</para>
- </listitem>
-
- <listitem>
- <para>Bug修复:如果在同一个模板之前没有正确的 <literal>#</literal> 标记,
- 错误的 <literal>#</literal> 标记被打印成静态的文本,而不会引发解析错误。
- 因为这个修复并不会100%的向后兼容,老版本中的行为会被保留下来,
- 除非你将 <literal>incompatible_enhancements</literal>
- (也就是<literal>Configuration.setIncompatibleEnhancements(String)</literal>)
- 设置成 <literal>"2.3.19"</literal> 或更高版本。</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Java部分的修改</title>
-
- <itemizedlist>
- <listitem>
- <para xml:id="v2319secfix"><emphasis>注意</emphasis>:
- 本次发布包含两个重要的安全修复,很明显,
- 这些安全问题会导致一些应用程序被利用。
- <emphasis>FreeMarker不能在所有的配置中解决这个问题,
- 所以请阅读下面的细节而不仅仅是升级FreeMarker!</emphasis>
- 理论上,这些修改并不是100%向后兼容的,
- 但是破坏任何东西也是不可能的。这两个修改是:</para>
-
- <itemizedlist>
- <listitem>
- <para>The character with character code 0
- (<literal>\u0000</literal>) is not allowed in template paths
- anymore. When a path contains it, FreeMarker behaves as if
- the template was not found.</para>
-
- <para>This is to fix the security problem where a template
- path like <literal>"secret.txt\u0000.ftl"</literal> is used
- to bypass extension filtering in an application. FreeMarker
- itself doesn't care about the extension, but some
- applications decide based on the extension if they will
- delegate a path to FreeMarker. When they do with such a
- path, the C/C++ implementation behind the storage mechanism
- may sees the path as <literal>"secret.txt"</literal> as the
- 0 terminates the string in C/C++, and thus load a non-FTL
- file as a template, returning the file contents to the
- attacker.</para>
-
- <para>Note that some HTTP servers, notably Tomcat and the
- Apache HTTP Server blocks URL-s where the URL contains 0
- (<literal>%00</literal>) outside the query string, thus this
- wasn't exploitable there through such Web URL-s. Some other
- HTTP servers however, like Jetty, doesn't block such
- URL-s.</para>
- </listitem>
-
- <listitem>
- <para><literal>ClassTemplateLoader</literal>, when it's
- created with base path <literal>"/"</literal> (like with
- <literal>new ClassTemplateLoader(someClass, "/")</literal>),
- will not allow template paths that contain colon earlier
- than any <literal>/</literal>, and will act like if the
- template was not found in such case.</para>
-
- <para>This is to fix the security problem where a template
- path like <literal>"file:/etc/secret"</literal> or
- <literal>"http://example.com/malware.ftl"</literal> is
- interpreted as a full URL by a
- <literal>java.net.URLClassLoader</literal> in the
- class-loader hierarchy, and thus allow loading files from
- these URL-s as templates. This is a quirk (or bug) of
- <literal>java.net.URLClassLoader</literal>, thus this
- problem only exists on systems that use such
- class-loaders.</para>
-
- <para>Beware, some frameworks use their own
- <literal>TemplateLoader</literal> implementations, and if
- those are vulnerable, they will remain so after updating
- FreeMarker too! Note that this exploit only works if the
- class-loader hierarchy contains an
- <literal>URLClassLoader</literal> and the class-loader is
- used to load templates without adding any prefix before the
- template path (other than <literal>"/"</literal>).</para>
- </listitem>
- </itemizedlist>
-
- <para>These security issues mostly only affect applications
- <emphasis>where the user (the visitor) can supply arbitrary
- template paths to the application</emphasis>. This is not the
- case with properly built MVC applications, as there only the MVC
- Controller can be addressed directly, and it's the Controller
- that specifies the template paths. But legacy MVC applications
- based on <link linkend="pgui_misc_servlet_model2">JSP
- Model-2</link> often expose the MVC Views as public URL-s ending
- with <literal>.ftl</literal>, thus allowing the user to give
- arbitrary paths to FreeMarker. Such applications should be
- secured with a <literal>security-constratint</literal> in
- <literal>web.xml</literal> as shown in the <link
- linkend="pgui_misc_servlet_model2">related Manual
- section</link>. This should be done regardless of the current
- security fixes.</para>
-
- <para>In general, you should not allow users to specify
- arbitrary template paths, or if you do allow that, you should be
- extra careful with the <literal>TemplateLoader</literal>
- used.</para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration</literal> has new methods:
- <literal>removeTemplateFromCache(...)</literal>. This will
- remove the given template for the given locale from the cache,
- so it will be re-loaded regardless of the template update delay
- when it's next time requested.</para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal> ignores setter methods
- from now when introspecting classes. They weren't used anyway,
- so they unnecessarily caused
- "<literal>java.beans.IntrospectionException</literal>: type
- mismatch between read and write methods" errors.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateClassResolver.SAFER_RESOLVER</literal>
- now disallows creating
- <literal>freemarker.template.utility.JythonRuntime</literal> and
- <literal>freemarker.template.utility.Execute</literal>. This
- change affects the behavior of the <link
- linkend="ref_builtin_new"><literal>new</literal> built-in</link>
- if FreeMarker was configured to use
- <literal>SAFER_RESOLVER</literal>, which is not the default
- until 2.4 and is hence improbable.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Calling varargs methods now indeed works.
- (Earlier it only worked for overloaded methods.)</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="https://sourceforge.net/tracker/index.php?func=detail&aid=1837697&group_id=794&atid=100794">[1837697]</link>
- <link
- xlink:href="https://sourceforge.net/tracker/index.php?func=detail&aid=2831150&group_id=794&atid=100794">[2831150]</link>
- <link
- xlink:href="https://sourceforge.net/tracker/index.php?func=detail&aid=3039096&group_id=794&atid=100794">[3039096]</link>
- <link
- xlink:href="https://sourceforge.net/tracker/index.php?func=detail&aid=3165425&group_id=794&atid=100794">[3165425]</link>:
- Jython support now works with Jython 2.2 and 2.5.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="https://sourceforge.net/tracker/index.php?func=detail&aid=3325103&group_id=794&atid=100794">[3325103]</link>:
- <literal>TemplateException</literal>-s and
- <literal>ParseException</literal>-s are now serializable.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_18">
- <title>2.3.18</title>
-
- <para>Date of release: 2011-05-21</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix <link
- xlink:href="https://sourceforge.net/tracker/?func=detail&aid=3304568&group_id=794&atid=100794">[3304568]</link>:
- 2.3.17 didn't find TLD-s in <literal>WEB-INF\lib\*.jar</literal>
- unless they were explicitly pointed in the
- <literal>web.xml</literal> with a <literal>taglib</literal>
- element. This bug was introduced in 2.3.17.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Added <literal>LICENSE.txt</literal> and
- <literal>NOTICE.txt</literal> to
- <literal>freemarker.jar</literal> under
- <literal>META-INF</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_17">
- <title>2.3.17</title>
-
- <para>Date of release: 2011-05-17</para>
-
- <para>It's possibly urgent to update to this version because of a
- <link linkend="v2317secfix">security fix</link>!</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>?seq_index_of</literal> and
- <literal>?seq_last_index_of</literal> now works on collections
- (<literal>freemarker.template.TemplateCollectionModel</literal>-s)
- too, not only on sequences
- (<literal>freemarker.template.TemplateSequenceModel</literal>-s).</para>
- </listitem>
-
- <listitem>
- <para><literal>?long</literal> now works for date, date-time or
- time values, and returns the milliseconds since the epoch (as
- <literal>java.util.Date.getTime()</literal>).</para>
- </listitem>
-
- <listitem>
- <para>To convert numbers (usually Java
- <literal>long</literal>-s) to date or date-time and time values,
- <literal>?number_to_date</literal>,
- <literal>?number_to_time</literal>,
- <literal>?number_to_datetime</literal> was added. <link
- linkend="ref_builtin_numToDate">See more here...</link>
- (Unfortunately, <literal>?date</literal> and like can't be
- extended to support this due to backward compatibility
- issues.)</para>
- </listitem>
-
- <listitem>
- <para>New built-ins to format numbers with ISO 8601 "extended"
- format regardless of the current date/time formatting settings,
- and even regardless of the current time zone setting. For
- example <literal>${myTimeStamp?iso_utc}</literal> will print
- something like <literal>2010-05-16T23:05:45Z</literal>. <link
- linkend="ref_builtin_date_iso">See more here...</link></para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_specvar">special variable</link>,
- <literal>now</literal>. This returns the current date-time.
- Usage examples: "<literal>Page generated: ${.now}</literal>",
- "<literal>Today is ${.now?date}</literal>", "<literal>The
- current time is ${.now?time}</literal>".</para>
- </listitem>
-
- <listitem>
- <para><literal>?sort</literal> and <literal>?sort_by</literal>
- now supports sorting boolean values.</para>
- </listitem>
-
- <listitem>
- <para>When using unsupported or unknown <link
- linkend="ref_builtin_string_flags">string built-in flags</link>,
- FreeMarker will now <link linkend="pgui_misc_logging">log</link>
- warnings (maximum 25 times per class-loader, to prevent flooding
- the log). It's certain that starting from FreeMarker 2.4 these
- will count as errors.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=3047201&group_id=794">[3047201]</link>:
- Using regular expressions (like with <literal>?match</literal>)
- could cause lockup in multi-threaded environment, also memory
- leakage when using dynamically generated regular
- expressions.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <literal>?seq_contains</literal>,
- <literal>?seq_index_of</literal> and
- <literal>?seq_last_index_of</literal> has failed with
- non-<literal>java.util.List</literal>
- <literal>java.util.Collection</literal>-s that are wrapped with
- pure <literal>BeansWrapper</literal> (not the
- <literal>DefaultObjectWrapper</literal>) as
- <literal>TemplateSequenceModel</literal>. (See also:
- <literal>getSupportsIndexedAccess()</literal> below)</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para xml:id="v2317secfix"><emphasis>Security fix</emphasis>:
- Using carefully crafted template names (template paths) that
- contain code point 0 (<literal>'\u0000'</literal>), it was
- possible to load files from outside the template root directory
- like if they were FreeMarker templates. The root of the problem
- is that the underlying native C/C++ part (which belongs to the
- Java platform or to the OS) interprets the 0 as the end of the
- string, while Java (and hence FreeMarker and the Servlet
- container) doesn't. Thus a path that looked safe for FreeMarker
- become unsafe on the lower level. The problem is present with
- all ways of loading templates by name
- (<literal>Configuration.getTemplate(<replaceable>...</replaceable>)</literal>,
- <literal><#include
- <replaceable>...</replaceable>></literal>,
- <literal><#import
- <replaceable>...</replaceable>></literal>).</para>
-
- <para>You are not affected if you don't allow users to upload
- templates and also at least one of these stands:</para>
-
- <itemizedlist>
- <listitem>
- <para>In your system users can't provide arbitrary strings
- as template names (template paths). For example, if users
- are only allowed to visit the URL-s that belong to the MVC
- Controller (like they can't visit <literal>*.ftl</literal>)
- then they can't suggest arbitrary template names.</para>
- </listitem>
-
- <listitem>
- <para>The template names are part of the path in the Web
- page URL, and your webserver or Servlet container disallows
- URL-s that contain <literal>%00</literal>, or terminate the
- URL at it before passing it to the servlets.</para>
- </listitem>
-
- <listitem>
- <para>You are using <literal>FileTemplateLoader</literal>
- and linking is not allowed in it (by default it isn't
- allowed).</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>FreeMarker now can log its messages directly using SLF4J
- or Apache Commons Logging. However, it will not use these logger
- libraries automatically, until 2.4; <link
- linkend="pgui_misc_logging">see more here...</link> But it's
- recommended to switch to SLF4J now.</para>
- </listitem>
-
- <listitem>
- <para>New setting: <literal>"auto_flush"</literal>,
- <literal>Configurable.setAutoFlush(boolean)</literal>. Sets
- whether the output <literal>Writer</literal> is automatically
- flushed at the end of <literal>Template.process(Object,
- Writer)</literal> (and its overloads). The default is
- <literal>true</literal>, which corresponds to the earlier
- behavior. Using <literal>false</literal> is needed for example
- when a Web page is composed from several boxes (like portlets,
- GUI panels, etc.) that aren't inserted with
- <literal>#include</literal> (or with similar directives) into a
- master FreeMarker template, rather they are all processed with a
- separate
- <literal>Template.process(<replaceable>...</replaceable>)</literal>
- call. In a such scenario the automatic flushes would commit the
- HTTP response after each box, hence interfering with full-page
- buffering, and also possibly decreasing performance with too
- frequent and too early response buffer flushes.</para>
- </listitem>
-
- <listitem>
- <para>Added new setting:
- <literal>Configuration.setNewBuiltinClassResolver(TemplateClassResolver)</literal>,
- or <literal>new_builtin_class_resolver</literal> property. This
- allows you to specify how the <link
- linkend="ref_builtin_new"><literal>new</literal> built-in</link>
- (like in <literal>"com.example.SomeClass"?new()</literal>)
- resolves classes and which classes are accessible at all. If you
- are allowing not-so-much-trusted users to upload templates, you
- should be definitely interested; see the Java API docs of
- <literal>freemarker.core.Configurable.setSetting</literal> and
- <literal>freemareker.template.Configuration.setNewBuiltinClassResolver</literal>.
- Otherwise it's still recommended to set this to
- <literal>TemplateClassResolver.SAFER_RESOLVER</literal> (or
- <literal>safer</literal> if you are using properties), although
- that's not 100% backward compatible (see Java API docs) .</para>
- </listitem>
-
- <listitem>
- <para>Added
- <literal>freemarker.cache.NullCacheStorage</literal>: Setting
- this as the cache storage in <literal>Configuration</literal>
- disables caching.</para>
- </listitem>
-
- <listitem>
- <para>Added <literal>getSupportsIndexedAccess()</literal> to
- <literal>freemarker.ext.beans.CollectionModel</literal>, so one
- can check if <literal>TemplateSequenceModel.get(int)</literal>
- will work with a particular <literal>CollectionModel</literal>
- instance or not.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&aid=2992265&group_id=794&atid=100794">[2992265]</link>:
- JSP <literal>FreeMarkerPageContext.include</literal> behaved
- incorrectly.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: When using FreeMarker's JSP support with JSP
- tags that use
- <literal>javax.servlet.jsp.PageContext.pushBody</literal> (like
- some Stripes tags), <literal>"ArrayIndexOutOfBoundsException:
- -1"</literal> occurred inside
- <literal>freemarker.ext.jsp.FreeMarkerPageContext.popWriter</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=3033015&group_id=794">[3033015]</link>:
- <literal>AllHttpScopesHashModel</literal> used
- <literal>WrappingTemplateModel.getDefaultObjectWrapper()</literal>
- for wrapping variables in the page scope, while used the
- user-specified <literal>ObjectWrapper</literal> for all other
- scopes (request, session, etc.). Now it uses the user-specified
- wrapper in the page scope as well.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed <link
- xlink:href="https://sourceforge.net/tracker/?func=detail&aid=3128073&group_id=794&atid=100794">[3128073]</link>:
- <literal>HashAdapther.containsKey(...)</literal> returned
- <literal>true</literal> for a key that doesn't exist when
- unwrapping the key has failed. As a side effect of the fix,
- <literal>BeansWrapper.CAN_NOT_UNWRAP</literal> is now private;
- earlier it was public by mistake.</para>
- </listitem>
-
- <listitem>
- <para>Big fixed <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&aid=3151085&group_id=794&atid=100794">[3151085]</link>:
- <literal>freemarker.jsp.TaglibFactory</literal> didn't locate
- tld files properly. This fix gives better compliance with JSP
- specification for resolving and loading tld files.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Unwrapping <literal>null</literal> with a
- <literal>BeansWrapper</literal> that had a custom null-model
- didn't result in <literal>null</literal>. Now both unwrapping
- <literal>null</literal> and the custom null-model gives
- <literal>null</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Log messages doesn't contain line-breaks (CR or LF)
- anymore and quote paths and other arbitrary text with Java
- string literal syntax that also escapes <literal><</literal>
- characters as <literal>\u003C</literal>. These address security
- concerns related to poor quality log appenders and buggy log
- readers. This change is mostly noticeable on template processing
- error entries, which will now quote the exception message. Note
- that how stack traces (the <literal>Throwable</literal> objects)
- are logged is still up to the logging framework you are
- using.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The DTD-s and XSD-s that are included in
- <literal>freemarker.jar</literal> under
- <literal>freemarker/ext/jsp</literal> are now under Apache
- Software License, Version 2. This is also clarified in the
- <literal>LICENSE.txt</literal>. Earlier these files had no clear
- license terms.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_16">
- <title>2.3.16</title>
-
- <para>Date of release: 2009-12-07</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixed a bug that caused incorrect unwrapping of sequences
- to Java arrays (<link
- xlink:href="https://sourceforge.net/tracker/?func=detail&aid=2105310&group_id=794&atid=100794">See
- bug report</link>)</para>
- </listitem>
-
- <listitem>
- <para>Fixed a bug that caused rounding of float and double
- values (<link
- xlink:href="https://sourceforge.net/tracker/?func=detail&aid=2503124&group_id=794&atid=100794">See
- bug report</link>)</para>
- </listitem>
-
- <listitem>
- <para>Created a new
- <literal>freemarker.runtime.attempt</literal> category and
- exceptions caught in <literal><#attempt></literal> blocks
- are logged into it at a DEBUG severity.</para>
- </listitem>
-
- <listitem>
- <para>Fixing the (ages old) problem of
- <literal>RhinoWrapper</literal> not working with all versions of
- Rhino because of binary incompatible change of Rhino's
- <literal>Undefined.instance</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Fixed bug where <literal>TextUtil.XMLEncNQG</literal>
- didn't escape <literal>]]></literal> as
- <literal>]]&gt;</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Fixed bug where the root directory couldn't be used as the
- template base directory, as
- <literal>FileTemplateLoader</literal> believed that the template
- is outside the base directory.</para>
- </listitem>
-
- <listitem>
- <para>Macro names can no longer be changed through the
- API.</para>
- </listitem>
-
- <listitem>
- <para>FreemarkerServlet now cooperates with Session Fixation
- Attack Protection in Spring Security, see <link
- xlink:href="https://sourceforge.net/projects/freemarker/forums/forum/2345/topic/3475868">
- forum discussion</link> for details.</para>
- </listitem>
-
- <listitem>
- <para>Substantially improved performance of the
- <literal><#return></literal> directive.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixed bug where
- <literal><replaceable>anXMLNode</replaceable>.@@markup</literal>
- and <literal>@@nested_markup</literal> didn't escape
- <literal>]]></literal> as
- <literal>]]&gt;</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_15">
- <title>2.3.15</title>
-
- <para>Date of release: 2008-12-16</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: Hash concatenation (like <literal>hash1 +
- hash2</literal>) shuffled the order of keys/values even if both
- hashes were ordered.</para>
- </listitem>
-
- <listitem>
- <para>In web pages that are based on the
- <literal>FreemarkerServlet</literal>, you can now use
- <literal><@include_page path="..."/></literal> to use
- servlet includes. See more <link
- linkend="pgui_misc_servlet_include">here...</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>BeansWrapper</literal> can automatically
- detect that classes were reloaded by JavaRebel.</para>
- </listitem>
-
- <listitem>
- <para>Fixed a bug that caused <literal>null</literal> to be
- returned from
- <literal>Environment.getCurrentEnvironment()</literal> while
- processing autoincludes and autoimports. (<link
- xlink:href="https://sourceforge.net/forum/message.php?msg_id=5531621">See
- bug report</link>)</para>
- </listitem>
-
- <listitem>
- <para>Fixed a bug that caused
- <literal>getObject(Object)</literal> method on POJOs to not be
- recognized as a general get method.</para>
- </listitem>
-
- <listitem>
- <para>Substantially improved performance of the
- <literal><#break></literal> directive.</para>
- </listitem>
-
- <listitem>
- <para><literal>DeepUnwrap</literal> now unwraps custom null
- model of the current object wrapper into a Java
- <literal>null</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_14">
- <title>2.3.14</title>
-
- <para>Date of release: 2008-09-01</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New built-in: <literal>xhtml</literal>. See more <link
- linkend="ref_builtin_xhtml">here...</link></para>
- </listitem>
-
- <listitem>
- <para>New special variable: <literal>template_name</literal>.
- See more <link linkend="ref_specvar">here...</link></para>
- </listitem>
-
- <listitem>
- <para>Now you can use the values of parameters as the defaults
- of other parameters, for example <literal><#macro section
- title label=title></literal>. In earlier versions it worked
- unreliably. There are no restriction regarding the order of
- parameters, like <literal><#macro section label=title
- title></literal> works too.</para>
- </listitem>
-
- <listitem>
- <para>Added a new <link
- linkend="ref_builtin_string_for_number">number format
- specifier</link>, <literal>computer</literal>. This uses the
- same formatting as
- <literal><replaceable>exp</replaceable>?c</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>The constructor to
- <literal>freemarker.ext.servlet.AllHttpScopesHashModel</literal>
- is now public, allowing it to be reused in 3rd party web
- frameworks.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.ext.beans.SimpleMapModel</literal> (unlike
- either <literal>freemarker.ext.beans.MapModel</literal> or
- <literal>freemarker.template.SimpleHash</literal>) didn't allow
- lookup by <literal>java.lang.Character</literal> key when passed
- a single-character string as a key.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: permissive unwrapping in
- <literal>freemarker.template.utility.DeepUnwrap</literal> class
- was not recursively permissive with elements of sequences and
- hashes.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>freemarker.ext.beans.MapModel</literal>
- returns <literal>BeansWrapper.wrap(null)</literal> instead of
- <literal>null</literal> for <literal>null</literal> values
- explicitly bound into the map.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Fixed a subtle bug with property getters of
- classes implementing a type-parametrized interface.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: A further corner case of <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1939742&group_id=794&atid=100794">[1939742]</link>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_13">
- <title>2.3.13</title>
-
- <para>Date of release: 2008-05-05</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New built-ins for rounding numbers:
- <literal>round</literal>, <literal>floor</literal>,
- <literal>ceiling</literal>. See more <link
- linkend="ref_builtin_rounding">here...</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para><link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1898300&group_id=794&atid=350794">[1898300]</link>,
- <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1818742&group_id=794&atid=350794">[1818742]</link>,
- <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1780882&group_id=794&atid=350794">[1780882]</link>:
- Reworked template caching mechanism for radically improved
- concurrent performance, with help from Azul Systems engineers.
- (Achieved 20x speedup with Struts2 webapps on a 128-CPU Azul
- device compared to 2.3.12.) Also, template loading (including
- parsing) errors are now cached, improving performance in
- applications that often try to get missing templates.</para>
- </listitem>
-
- <listitem>
- <para><link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1892546&group_id=794&atid=100794">[1892546]</link>
- Allow for custom <literal>TemplateLoader</literal> in
- <literal>FreemarkerServlet</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1725107&group_id=794&atid=100794">[1725107]</link>
- Using the FreeMarker JSP taglib support with Servlet 2.4 may
- generates XML validation warnings.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1939742&group_id=794&atid=100794">[1939742]</link>
- <literal>ConcurrentModificationException</literal> on accessing
- nonexistent <literal>SimpleHash</literal> entries in a
- loop</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1902012&group_id=794&atid=100794">[1902012]</link>
- <literal>IteratorModel</literal> eats exception causes</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <literal><#assign
- x></#assign></literal> (empty nested content) has
- caused <literal>NullPointerException</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1926150&group_id=794&atid=100794">[1926150]</link>
- <literal>CachedTemplate</literal> should be serializable</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_12">
- <title>2.3.12</title>
-
- <para>Date of release: 2008-02-03</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1857161&group_id=794&atid=100794">[1857161]</link>
- JSP <literal>SimpleTag</literal> support was broken in
- 2.3.11.</para>
- </listitem>
-
- <listitem>
- <para>In the templates, now you can conveniently call Java
- methods that use the Java 5 varargs feature (variable-length
- argument lists). Also the overloaded-method chooser logic now
- considers vararg methods more intelligently.</para>
- </listitem>
-
- <listitem>
- <para>Enum constants are now identified by their
- <literal>name()</literal> instead of by their
- <literal>toString()</literal> (because the latter can be
- overridden in subclasses). This doesn't affect the way enum
- constants are printed; of course that still uses
- <literal>toString()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Messages in parser exceptions now display the name of the
- template.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_11">
- <title>2.3.11</title>
-
- <para>Date of release: 2007-12-04</para>
-
- <para>This release contains several performance and usability
- improvements.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1687248&group_id=794&atid=100794">[1687248]</link>
- <emphasis role="bold">Warning! This bugfix may breaks some
- templates!</emphasis> Fixed the bugs of the <link
- linkend="ref_builtin_c"><literal>c</literal> built-in</link>
- (<literal>?c</literal>) that sometimes caused whole numbers to
- be formatted with ``.0'' at the end (like: 1.0), and caused
- numbers sometimes formatted to exponential form (like 4E-20).
- From now whole numbers will never use decimal dot (not even if
- the wrapped number is a <literal>double</literal>; remember, the
- template language knows only a single numerical type), and
- exponential form will never be used either. Also, the maximum
- number of digits after the decimal dot was limited to 16, so
- numbers smaller than 1E-16 will be shown as 0.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>FreeMarker now has much better JSP 2.0 and JSP 2.1
- compliance. Most notably, the JSP 2.0
- <literal>SimpleTag</literal> interface is now supported.
- Additionally, even when run in an environment that doesn't have
- its own JSP implementation, the FreeMarker JSP runtime will make
- available its own implementation of
- <literal>JspFactory</literal> and
- <literal>JspEngineInfo</literal> to tags when JSP 2.0 API JAR is
- available in classpath, as well as an implementation of
- <literal>JspApplicationContext</literal> when JSP 2.1 API JAR is
- available in classpath.</para>
- </listitem>
-
- <listitem>
- <para>A new model interface,
- <literal>TemplateDirectiveModel</literal> provides an easier
- paradigm for implementing user-defined directives than
- <literal>TemplateTransformModel</literal> did previously.
- <literal>TemplateTransformModel</literal> will be
- deprecated.</para>
- </listitem>
-
- <listitem>
- <para>FreeMarker now finds the Xalan-based XPath support
- included in Sun JRE/JDK 5 and 6, so no separate Xalan jar is
- required for the XPath support to work. (However, we recommend
- Jaxen over Xalan, as the FreeMarker XPath support is more
- complete with that. Of course for that the Jaxen jar is still
- needed.)</para>
- </listitem>
-
- <listitem>
- <para>Wrapping performance of <literal>BeansWrapper</literal>
- has been significantly improved by eliminating repetitive
- execution of various class tests.</para>
-
- <para><emphasis role="bold">Note for
- <literal>BeansWrapper</literal> customizers:</emphasis>
- subclasses of <literal>BeansWrapper</literal> that previously
- overrode <literal>getInstance(Object, ModelFactory)</literal>
- method should now instead override
- <literal>getModelFactory(Class)</literal> to take advantage of
- this improvement. Overriding the old method still works, but it
- will not take advantage of the performance improvement.</para>
- </listitem>
-
- <listitem>
- <para>Memory footprint of a wrapper created by
- <literal>BeansWrapper</literal> has been reduced (by a size of
- one default-sized <literal>HashMap</literal>) until methods or
- indexed properties are accessed on it (simple properties can be
- accessed without increasing memory footprint).</para>
- </listitem>
-
- <listitem>
- <para>Rhino objects can be used in templates as scalars,
- numbers, and booleans, following the JavaScript conversion
- semantics for these types.</para>
- </listitem>
-
- <listitem>
- <para><literal>.data_model</literal> is now a
- <literal>TemplatHashModelEx</literal> when possible. This means
- that the list of the data-model variable names usually can be
- get with <literal>.data_model?keys</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>FileTemplateLoader</literal> can now optionally
- allow following symlinks that point out of the base directory.
- It is disabled by default for backward compatibility.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1670887&group_id=794&atid=100794">[1670887]</link>
- <literal>TaglibFactory</literal> taglib matching did not follow
- JSP 1.2 FCS.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1754320&group_id=794&atid=100794">[1754320]</link>
- Bug in <literal>setXPathSupportClass</literal> prevented
- plugging in a user-supplied <literal>XPathSupport</literal>
- implementation.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1803298&group_id=794&atid=100794">[1803298]</link>
- Parser error while parsing macro with loop variables</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1824122&group_id=794&atid=100794">[1824122]</link>
- Loading templates from JAR files could lead to leaking of file
- handles (due to a bug in the Java API implementation of
- Sun).</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: Cached template is now removed from the cache
- if the re-loading of the modified template file fails, so no
- staled template is served.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Documentation changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Substantial reworkings in the Template Authors's Guide
- (which was previously called Designer's Guide), especially in
- the Getting Started section.</para>
- </listitem>
-
- <listitem>
- <para><literal>#{...}</literal> is documented as deprected
- construct from now.</para>
- </listitem>
-
- <listitem>
- <para>The "transform" term is now removed from the
- documentation. Instead the more general "user-defined directive"
- term is used, which encompasses macros,
- <literal>TemplateTransformModel</literal>-s and the new
- <literal>TemplateDirectiveModel</literal>-s, which are just
- different ways of implementing user-defined directives.</para>
- </listitem>
-
- <listitem>
- <para>Some more minor improvements in the Manual.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_10">
- <title>2.3.10</title>
-
- <para>Date of release: 2007-04-20</para>
-
- <para>This release contains several important bugfixes.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>[1589245] <literal>MultiTemplateLoader</literal> clears
- its internal cached data (used for optimizing subsequent lookups
- of the same template) when
- <literal>Configuration.clearTemplateCache()</literal> is
- invoked.</para>
- </listitem>
-
- <listitem>
- <para>[1619257] A bug that caused an exception when
- <literal>strict_bean_model</literal> was used in a FreeMarker
- configuration <literal>Properties</literal> object or in the
- <literal><#setting .../></literal> directive has been
- fixed.</para>
- </listitem>
-
- <listitem>
- <para>[1685176] A bug that caused
- <literal>StackOverflowError</literal> in certain interactions of
- garbage collector with MRU cache under Sun's Java 6 JVM has been
- fixed.</para>
- </listitem>
-
- <listitem>
- <para>[1686955] When <literal>ResourceBundleModel</literal>
- constructs <literal>MessageFormat</literal> objects, it passes
- them its own locale. <link linkend="beanswrapper_method">More
- info...</link></para>
- </listitem>
-
- <listitem>
- <para>[1691432] A bug that caused
- <literal>BeansWrapper.EXPOSE_SAFE</literal> to be no safer than
- <literal>BeansWrapper.EXPOSE_ALL</literal> has been
- fixed.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>[1628550] You can now use
- <literal>dateExp?string.full</literal> for formatting dates
- using Java built-in format
- <literal>java.util.Date.FULL</literal> <link
- linkend="ref_builtin_string_for_date">More info...</link></para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_9">
- <title>2.3.9</title>
-
- <para>Date of release: 2007-01-23</para>
-
- <para>This release contains support for accessing JDK 1.5 enums and
- public fields of classes from the templates through the
- BeansWrapper.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>BeansWrapper</literal> can now expose public
- fields of objects to the template if you call the
- <literal>setExposeFields(true)</literal> on it. <link
- linkend="beanswrapper_hash">More info...</link></para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal> can now pass any sequence
- model to Java methods expecting a
- <literal>java.util.Collection</literal> or a native Java array
- (including primitive arrays). <link
- linkend="beanswrapper_hash">More info...</link></para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal> can now pass any sequence
- and collection model to Java methods expecting a
- <literal>java.lang.Iterable</literal>. <link
- linkend="beanswrapper_hash">More info...</link></para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal> can now unwrap numeric
- models into correct target types when passing to Java methods
- expecting a primitive or boxed number. Use of various <link
- linkend="ref_builtins_expert">expert built-ins</link> to
- manually coerce the types becomes mostly unnecessary.</para>
- </listitem>
-
- <listitem>
- <para>Fixed a bug where <literal>BeansWrapper</literal> would
- pass a <literal>java.util.Collection</literal> to a method
- expecting a <literal>java.util.Set</literal> in certain rare
- cases. <link linkend="beanswrapper_hash">More
- info...</link></para>
- </listitem>
-
- <listitem>
- <para>Support for JDK 1.5 enums in
- <literal>BeansWrapper</literal> and
- <literal>DefaultObjectWrapper</literal>. By calling the
- <literal>getEnumModels()</literal> method, you can retrieve a
- hash model that is keyed by class names and allows access to
- enumerated values. I.e. if you bind this hash model under name
- <literal>enums</literal> in the data-model, you can write
- expressions like
- <literal>enums["java.math.RoundingMode"].UP</literal> in the
- template. The enum values can be used as scalars and support
- equality and inequality comparisons. <link
- linkend="jdk_15_enums">More info...</link></para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.ext.rhino.RhinoWrapper</literal> now
- correctly translates Rhino <literal>Undefined</literal>
- instance, <literal>UniqueTag.NOT_FOUND</literal>, and
- <literal>UniqueTag.NULL</literal> to FreeMarker undefined
- value.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_8">
- <title>2.3.8</title>
-
- <para>Date of release: 2006-07-09</para>
-
- <para>This release substantially improves the JSP 2.0 compatibility.
- (For those who have seen the same points in 2.3.7: Sorry, the version
- history was incorrect... those JSP 2.0 related changes were not in the
- release yet.)</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>JSP support improvement: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1326058&group_id=794">[1326058]</link>
- Added support for <literal>DynamicAttributes</literal> (new in
- JSP 2.0)</para>
- </listitem>
-
- <listitem>
- <para>JSP support improvement: Added support for
- <literal>pushBody()</literal>/<literal>popBody()</literal> in
- <literal>FreemarkerPageContext</literal></para>
- </listitem>
-
- <listitem>
- <para>JSP support improvement: Added support for
- <literal>getVariableResolver()</literal> (new in JSP
- 2.0).</para>
- </listitem>
-
- <listitem>
- <para>JSP support improvement: Added support for
- <literal>include(String, boolean)</literal> (new in JSP
- 2.0).</para>
- </listitem>
-
- <listitem>
- <para>JSP support improvement: Added support for
- <literal>getExpressionEvaluator()</literal> (new in JSP 2.0).
- However, it will need Apache commons-el in the class path, or
- else this method will not work (it's optional). Note that EL
- support is not needed in principle, since FreeMarker has it's
- own expression language. But some custom JSP 2 tags may still
- want to use this method, after all it's in the JSP 2 API.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_7">
- <title>2.3.7</title>
-
- <para>Date of release: 2006-06-23</para>
-
- <para>This release, compared to 2.3.7 RC1, contains new operators for
- handling null/missing variables, , the <literal>substring</literal>
- built-in, and some more bugfixes. Note that 2.3.7 RC1 has a long
- change log, so you may want to <link linkend="versions_2_3_7rc1">read
- that</link> too.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>seq_contains</literal> built-in now handles
- <literal>TemplateCollectionModel</literal>-s as well.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: In 2.3.7 RC1
- <literal>FreemarkerServlet</literal> has always died with
- <literal>NullPointerException</literal> during
- initialization.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>3 new operators were added for terser missing variable
- handling. These operators make the <literal>default</literal>,
- <literal>exists</literal> and <literal>if_exists</literal>
- built-ins deprecated. (The parser doesn't issue any warning
- messages when you use deprecated built-ins, and they are still
- working.):</para>
-
- <itemizedlist>
- <listitem>
- <para><literal><replaceable>exp1</replaceable>!<replaceable>exp2</replaceable></literal>
- is near equivalent with
- <literal><replaceable>exp1</replaceable>?default(<replaceable>exp2</replaceable>)</literal>,
- also
- <literal>(<replaceable>exp1</replaceable>)!<replaceable>exp2</replaceable></literal>
- is near equivalent with
- <literal>(<replaceable>exp1</replaceable>)?default(<replaceable>exp2</replaceable>)</literal>.
- The only difference is that this new operator doesn't
- evaluate the
- <literal><replaceable>exp2</replaceable></literal> when the
- default value is not needed.</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>exp1</replaceable>!</literal> is
- similar to
- <literal><replaceable>exp1</replaceable>?if_exists</literal>,
- also <literal>(<replaceable>exp1</replaceable>)!</literal>
- is similar to
- <literal>(<replaceable>exp1</replaceable>)?if_exists</literal>.
- The difference is that with this new operator the default
- value is an empty string and an empty list and empty hash at
- the same time (multi-type variable), while with
- <literal>if_exists</literal> the default value was an empty
- string and an empty list and empty hash and boolean
- <literal>false</literal> and a transform that does nothing
- and ignores all parameters at the same time.</para>
- </listitem>
-
- <listitem>
- <para><literal><replaceable>exp1</replaceable>??</literal>
- is equivalent with
- <literal><replaceable>exp1</replaceable>?exists</literal>,
- also <literal>(<replaceable>exp1</replaceable>)??</literal>
- is equivalent with with
- <literal>(<replaceable>exp1</replaceable>)?exists</literal>.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New built-in:
- <literal><replaceable>exp</replaceable>?substring(<replaceable>from</replaceable>,
- <replaceable>toExclusive</replaceable>)</literal>, also callable
- as
- <literal><replaceable>exp</replaceable>?substring(<replaceable>from</replaceable>)</literal>.
- Getting substrings was possible for a long time like
- <literal><replaceable>myString</replaceable>[<replaceable>from</replaceable>..<replaceable>toInclusive</replaceable>]</literal>
- and
- <literal><replaceable>myString</replaceable>[<replaceable>from</replaceable>..]</literal>,
- but
- <literal><replaceable>myString</replaceable>?substring(<replaceable>from</replaceable>,
- <replaceable>toExclusive</replaceable>)</literal> and
- <literal><replaceable>myString</replaceable>?substring(<replaceable>from</replaceable>)</literal>
- has the advantage that it has an exclusive end, which is more
- practical. (Edit: Since 2.3.21 ranges are the preferred way
- again, as it has
- <literal><replaceable>myString</replaceable>[<replaceable>from</replaceable>..<<replaceable>toExclusive</replaceable>]</literal>.)
- Sequence (list) slices still has to be get with the old syntax,
- since <literal>substring</literal> only applies to strings.
- Please note that the ``to'' parameter is 1 greater with this new
- builtin, as it is an exclusive index. Further difference is that
- the <literal>substring</literal> built-in requires that the
- ``from'' index is less than or equal to the ``to'' index. So 0
- length substrings are possible now, but not reversed
- substrings.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1487694&group_id=794">[1487694]</link>
- malfunction when the <literal>recover</literal> directive has no
- nested content</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_7rc1">
- <title>2.3.7 RC1</title>
-
- <para>Date of release: 2006-04-27</para>
-
- <para>This release contains many bugfixes and some
- <literal>FreemarkerServlet</literal> related improvements. It's a
- Release Candidate, which means that it shouldn't be used in production
- environment yet. We recommend this release for development, however.
- Please test it.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>FreemarkerServlet</literal> improvement:
- <literal>AllHttpScopesHashModel</literal> is now public, so you
- can add unlisted variables to the data-model.</para>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> improvement: When it
- throws a <literal>ServletException</literal>, the J2SE 1.4 cause
- exception is now set under J2SE 1.4.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1469275&group_id=794">[1469275]</link>
- <literal>NullPointerException</literal> when using
- <literal>BeansWrapper</literal> with reloaded classes</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1449467&group_id=794">[1449467]</link>
- <literal>HttpSessionHashModel</literal> is not
- <literal>Serializable</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1435113&group_id=794">[1435113]</link>
- Error in <literal>BeanWrapper</literal> with indexed
- properties</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1411705&group_id=794">[1411705]</link>
- Acquisition bug in <literal>TemplateCache</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1459699&group_id=794&atid=100794">[1459699]</link>
- Tag syntax can't set with
- <literal>Configuration.setSetting(String,
- String)</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1473403&group_id=794">[1473403]</link>
- <literal>ReturnInstruction.Return</literal> should be
- public</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1463664&group_id=794">[1463664]</link>kup
- <literal>[/#noparse]</literal> is printed out</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_6">
- <title>2.3.6</title>
-
- <para>Date of release: 2006-03-15</para>
-
- <para>Quick release that fixes a serious bug of 2.3.5, days after its
- release. So for the recently added new features please <link
- linkend="versions_2_3_5">see the section of 2.3.5.</link></para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: In FreeMarker 2.3.5 only, when you read a bean
- property for the second time, FreeMarker will say that it's
- missing (null).</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_5">
- <title>2.3.5</title>
-
- <para>Date of release: 2006-03-11</para>
-
- <para><emphasis>This release was withdrawn because of a serious bug in
- it. Please don't use it! Of course, all new features of it are
- included in FreeMarker 2.3.6.</emphasis></para>
-
- <para>A few new features and several bugfixes.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1435847&group_id=794">[1435847]</link>
- Alternative syntax doesn't work for comments</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: With the new square bracket syntax, the tag
- could be closed with <literal>></literal>. Now it can be
- closed with <literal>]</literal> only.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1324020&group_id=794">[1324020]</link>
- <literal>ParseException</literal> with the
- <literal>ftl</literal> directive if it wasn't in its own
- line</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1404033&group_id=794">[1404033]</link>
- <literal>eval</literal> built-in fails with hash
- concatenation</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>A new <literal>Configuration</literal> level setting,
- <literal>tagSyntax</literal> was added. This determines the
- syntax of the templates (angle bracket syntax VS <link
- linkend="dgui_misc_alternativesyntax">square bracket
- syntax</link>) that has no <literal>ftl</literal> directive in
- it. So now you can choose to use the new square bracket syntax
- by default. However, the recommended is to use auto-detection
- (<literal>yourConfig.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX)</literal>),
- because that will be the default starting from 2.4.
- Auto-detection chooses syntax based on the syntax of the first
- FreeMarker tag of the template (could be any FreeMarker tag, not
- just <literal>ftl</literal>). Note that as with the previous
- version, if a the template uses <literal>ftl</literal>
- directive, then the syntax of the <literal>ftl</literal>
- directive determines the syntax of the template, and the
- <literal>tagSyntax</literal> setting is ignored.</para>
- </listitem>
-
- <listitem>
- <para>Now <literal>BeansWrapper</literal>,
- <literal>DefaultObjectWrapper</literal> and
- <literal>SimpleObjectWrapper</literal> support lookup with 1
- character long strings in <literal>Map</literal>-s (like
- <literal>myHash["a"]</literal>) that use
- <literal>Character</literal> keys. Simply, as a special case,
- when a hash lookup fails on a string that is 1 character long,
- it checks for the <literal>Character</literal> key in the
- underlying map. (Bug tracker entry <link
- xlink:href="http://sourceforge.net/tracker/?func=detail&atid=100794&aid=1299045&group_id=794">[1299045]</link>
- FreeMarker doesn't support map lookup with Character
- keys.)</para>
- </listitem>
-
- <listitem>
- <para>A new property, <literal>strict</literal> was added to
- <literal>BeansWrapper</literal>,
- <literal>DefaultObjectWrapper</literal> and
- <literal>SimpleObjectWrapper</literal>. If this property is
- <literal>true</literal> then an attempt to read a bean propertly
- in the template (like <literal>myBean.aProperty</literal>) that
- doesn't exist in the bean class (as opposed to just holding
- <literal>null</literal> value) will cause
- <literal>InvalidPropertyException</literal>, which can't be
- suppressed in the template (not even with
- <literal>myBean.noSuchProperty?default('something')</literal>).
- This way <literal>?default('something')</literal> and
- <literal>?exists</literal> and similar built-ins can be used to
- handle existing properties whose value is
- <literal>null</literal>, without the risk of hiding typos in the
- property names. Typos will always cause error. But mind you, it
- goes against the basic approach of FreeMarker, so use this
- feature only if you really know what are you doing.</para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1426227&group_id=794&atid=100794">[1426227]</link>
- <literal>NullPointerException</literal> in
- <literal>printStackTrace(...)</literal></para>
- </listitem>
-
- <listitem>
- <para>Bug fixed: <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1386193&group_id=794&atid=100794">[1386193]</link>
- Division by zero in <literal>ArithmeticEngine</literal></para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_4">
- <title>2.3.4</title>
-
- <para>Date of release: 2005-10-10</para>
-
- <para>Some new features and bugfixes.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Now you can use <literal>[</literal> and
- <literal>]</literal> instead of <literal><</literal> and
- <literal>></literal> for the FreeMarker tags. For example you
- can write <literal>[#if
- loggedIn]<replaceable>...</replaceable>[/#if]</literal> and
- <literal>[@myMacro /]</literal>. <link
- linkend="dgui_misc_alternativesyntax">More info...</link></para>
- </listitem>
-
- <listitem>
- <para>Bugfix: the <literal>has_content</literal> built-in
- returned <literal>false</literal> for number, date and boolean
- values (if the value was not a multi-type value that is also a
- sequence or collection or hash or string). Now it always returns
- <literal>true</literal> for a number, date or boolean values
- (except if the value is also a sequence or collection or hash or
- string, because then it will be examined only like that).</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: the parameterless constructor of the
- <literal>ClassTemplateLoader</literal> didn't worked.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The Jython wrapper didn't wrapped
- <literal>java.util.Date</literal> objects well. Now it wraps
- them with <literal>BeanWrapper</literal> to
- <literal>TemplateDateModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: the <literal>include</literal> directive was
- blamed when the included file had syntax error.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Minor Manual fixes.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_3">
- <title>2.3.3</title>
-
- <para>Date of release: 2005-06-23</para>
-
- <para>Some new features and lot of bugfixes.</para>
-
- <para>Attention:</para>
-
- <itemizedlist>
- <listitem>
- <para>If you are using the Log4J logging, from now at least Log4J
- 1.2 is required. This is because of incompatible changes in the
- Log4J API.</para>
- </listitem>
-
- <listitem>
- <para>If you build FreeMarker yourself: from now at least JavaCC
- 3.2 (instead of JavaCC 2.1) and at least Ant 1.6.1 (instead of Ant
- 1.5.x) is required. This doesn't affect users who use the
- <literal>freemarker.jar</literal> comes with the
- distribution.</para>
- </listitem>
- </itemizedlist>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New built-in for formatting numbers for ``computer
- audience'' as opposed to human audience: <link
- linkend="ref_builtin_c"><literal>c</literal></link>. It should
- be used for numbers that must use Java language formatting
- regardless of the number format and locale settings, like for a
- database record ID used as the part of an URL or as invisible
- field value in a HTML form, or for printing CSS/JavaScript
- numerical literals.</para>
- </listitem>
-
- <listitem>
- <para>New built-in for the columnar/tabular displaying of
- sequences: <link
- linkend="ref_builtin_chunk"><literal>chunk</literal></link>.</para>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="dgui_template_exp_seqenceop_slice">sequence
- slice</link> and substring operators now allow the omitting of
- the last index, in which case it defaults to the index of the
- last sequence item or character. Example:
- <literal>products[2..]</literal>. (Also, <link
- linkend="dgui_template_exp_direct_seuqence">numerical range
- literals</link> now allow the omitting of the final number, in
- which case it defaults to infinity. Example:
- <literal>5..</literal>.)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>?replace</literal> has worked forever if
- the string to replace was <literal>""</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>New template loader:
- <literal>freemarker.cache.StringTemplateLoader</literal>. It
- uses a <literal>Map</literal> with <literal>Strings</literal> as
- its source of templates. See more in the JavaDoc.</para>
- </listitem>
-
- <listitem>
- <para>Experimental Rhino support: FreeMarker now comes with an
- experimental object wrapper for Rhino (Java ECMAScript
- implementation):
- <literal>freemarker.ext.rhino.RhinoWrapper</literal></para>
- </listitem>
-
- <listitem>
- <para>Some new utility methods for
- <literal>Simple<replaceable>Xxx</replaceable></literal> classes:
- <literal>SimpleHash.toMap()</literal>,
- <literal>SimpleSequence.toList()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: FTL literals and any other
- <literal>SimpleSequnce</literal>-s, and
- <literal>SimpleHash</literal>-es now can be used as parameters
- to the FreeMarker-unaware Java methods that are exposed by
- <literal>DefaultWrapper</literal> or
- <literal>BeansWrapper</literal>. That is, the method parameters
- are automatically converted from
- <literal>Template<replaceable>Type</replaceable>Model</literal>-s
- to <literal>java.util.Map</literal> and
- <literal>java.util.List</literal> respectively.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The JSP support now works in JSP 2 compliant
- containers as well. No, it doesn't support the new features of
- JSP 2 yet, it's just that the JSP 1.2 taglib support has not
- worked in JSP 2 containers.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The
- <literal>Configuration.setOutputEncoding</literal> and
- <literal>setURLEscapingCharset</literal> methods died with
- <literal>NullPointerException</literal> when you tried to set
- the setting value to <literal>null</literal>, which is legal for
- these settings.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.template.utility.StringUtil.replace(...)</literal>
- has worked forever if the string to replace was
- <literal>""</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The Log4J logging was updated to be compatible
- with the upcoming Log4J 1.3. Note that now FreeMarker will need
- at least Log4J 1.2.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: FreeMarker didn't built from the source code on
- J2SE 1.5, because of the introduction of the
- <literal>enum</literal> keyword.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The return value of
- <literal>SimpleSequence.synchronizedWrapper()</literal> was not
- properly synchronized. Same with
- <literal>SimpleHash.synchronizedWrapper()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Problem with <literal>BeansWrapper</literal> and
- overridden bean methods/properties. (Details: bug-tracker entry
- <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1217661&group_id=794&atid=100794">#1217661</link>
- and <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1166533&group_id=794&atid=100794">#1166533</link>)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Can't access JSP taglibs if
- <literal>Request</literal> attribute is defined in the
- data-model (Details: bug-tracker entry <link
- xlink:href="http://sourceforge.net/tracker/index.php?func=detail&aid=1202918&group_id=794&atid=100794">#1202918</link>).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Various minor parser glitches were fixed.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Manual improvements, especially in the FAQ.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_2">
- <title>2.3.2</title>
-
- <para>Date of release: 2005-01-22</para>
-
- <para>Bugfix release.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: If you use JSP taglibs in FreeMarker templates,
- FreeMarker possibly tried to get DTD-s from the Sun Web site
- because of a bug introduced with FreeMarker 2.3.1. This was a
- serious problem since if your server is offline or the Sun Web
- site becomes temporarily inaccessible the templates that are
- using JSP taglibs will possibly die with error.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>DefaultObjectWrapper</literal> has
- ignored the value of the <literal>nullModel</literal> property.
- (Note that it's discouraged to use a ``null model''.)</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_3_1">
- <title>2.3.1</title>
-
- <para>Date of release: 2005-01-04</para>
-
- <para>Maintenance (with some important new features) and bugfix
- release.</para>
-
- <section>
- <title>Possible backward compatibility issue</title>
-
- <para>There is a bugfix that may affect the behavior of you Web
- application if you use JSP tags in FreeMarker templates:
- FreeMarker's implementation of
- <literal>javax.servlet.jsp.PageContext.getSession()</literal> was
- incorrect. The <literal>getSession()</literal> method is a
- convenience method by which the custom tag can get the current
- <literal>HttpSession</literal> object (possibly
- <literal>null</literal> if there is no session). Till now, if the
- session didn't existed then it has created it automatically, so it
- never returned <literal>null</literal>. This was a bug, so starting
- from 2.3.1 it never creates the session, just returns
- <literal>null</literal> if it doesn't exist. The old incorrect
- behavior could cause page rendering to fail if the method is called
- after the page is partially flushed. But beware, the old behavior
- has possibly hidden some bugs of the Web application, where it
- forgot to create the session, so with the new correct behavior you
- may face malfunction caused by previously cloaked bugs of the Web
- application. (It's the task of the MVC Controller to create the
- session, except if the JSP tag that needs a session is written so it
- creates it automatically, but then it doesn't expects that
- <literal>getSession()</literal> will do it.)</para>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New built-in: <link
- linkend="ref_builtin_url"><literal>url</literal></link>. This
- built-in can be used for URL escaping. Note, however, that to
- use this built-in conveniently, the software that encapsulates
- FreeMarker has to be 2.3.1 aware (programmers will find more
- info bellow...).</para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_specvar">special variables</link>:
- <literal>output_encoding</literal> and
- <literal>url_escaping_charset</literal>. Note, however, that to
- use these, the software that encapsulates FreeMarker has to be
- 2.3.1 aware (programmers will find more info bellow...).</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for sequences: <link
- linkend="ref_builtin_seq_contains"><literal>seq_contains</literal></link>,
- <link
- linkend="ref_builtin_seq_index_of"><literal>seq_index_of</literal></link>,
- <link
- linkend="ref_builtin_seq_last_index_of"><literal>seq_last_index_of</literal></link>.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for strings: <link
- linkend="ref_builtin_left_pad"><literal>left_pad</literal></link>,
- <link
- linkend="ref_builtin_right_pad"><literal>right_pad</literal></link>
- and <link
- linkend="ref_builtin_contains"><literal>contains</literal></link>.</para>
- </listitem>
-
- <listitem>
- <para>New directive: <link
- linkend="ref.directive.attempt"><literal>attempt</literal>/<literal>recover</literal></link></para>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="ref_builtin_js_string"><literal>js_string</literal>
- built-in</link> now escapes <literal>></literal> as
- <literal>\></literal> (to avoid
- <literal></script></literal>).</para>
- </listitem>
-
- <listitem>
- <para>The <literal>sort</literal> and <literal>sort_by</literal>
- built-ins now can sort by date values. Also,
- <literal>sort_by</literal> built-in now can sort by the
- subvarible of a subvariable of a subvariable... etc. for any
- level depth. (<link
- linkend="ref_builtin_sort_by">Details...</link>)</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.TemplateExceptionHandler.HTML_DEBUG_HANDLER</literal>
- now prints more HTML-context-proof messages.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>New setting: <literal>output_encoding</literal>. This
- setting is used for informing FreeMarker about the charset that
- the enclosing software (as a Web application framework) uses for
- the output of FreeMarker. It's undefined by default, and
- although it is not strictly required to set it, the enclosing
- software should do so. This setting must be set if templates
- want to use the new <literal>output_encoding</literal> special
- variable, and possibly if they want to use the new
- <literal>url</literal> built-in. Note that the FreeMarker API
- allows you to set settings for each template execution
- individually (look at
- <literal>Template.createProcessingEnvironment(...)</literal>).</para>
- </listitem>
-
- <listitem>
- <para>New setting: <literal>url_escaping_charset</literal>. This
- is the charset used for calculating the escaped parts
- (<literal>%<replaceable>XX</replaceable></literal>) when you do
- URL escaping with the new <literal>url</literal> built-in. If it
- is not set, then the <literal>url</literal> built-in uses the
- value of the <literal>output_encoding</literal> setting, and if
- that's not set either, then the parameterless version of
- <literal>url</literal> built-in (<literal>${foo?url}</literal>)
- can't be used.</para>
- </listitem>
-
- <listitem>
- <para>Using the singleton (static)
- <literal>Configuration</literal> instance is clearly a bad
- practice, so related methods are now deprecated, and the Manual
- was adjusted, and the <literal>FreemarkerXmlTask</literal> was
- updated as well.</para>
- </listitem>
-
- <listitem>
- <para>The
- <literal>freemarker.template.utility.Constants</literal> class
- was added that contains various static final fields that store
- frequently used constant <literal>TemplateModel</literal>
- values, as <literal>EMPTY_SEQUENCE</literal>,
- <literal>ZERO</literal>, ...etc.</para>
- </listitem>
-
- <listitem>
- <para>When using <literal>SecurityManager</literal> with
- FreeMarker, accessing system properties may caused
- AccessControlException. Now such exceptions are catched and
- logged with warning level, and the default value of the property
- is returned.</para>
- </listitem>
-
- <listitem>
- <para>The needles <literal>InvocationTargetException</literal>
- is now removed from the exception cause trace in certain
- cases.</para>
- </listitem>
-
- <listitem>
- <para>Added a dirty hack that prints
- <literal>ServletException</literal> root cause in
- <literal>TemplateException</literal>'s stack trace if that's the
- direct cause exception of the
- <literal>TemplateException</literal>, despite the poorly written
- <literal>ServletException</literal> class.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: FreeMarker's implementation of
- <literal>javax.servlet.jsp.PageContext.getSession()</literal>
- was incorrect. The <literal>getSession()</literal> method is a
- convenience method by which the custom tag can get the current
- <literal>HttpSession</literal> object (possibly
- <literal>null</literal> if there is no session). Till now, if
- the session didn't existed then it has created it automatically,
- so it never returned <literal>null</literal>. This was a bug, so
- starting from 2.3.1 it never creates the session, just returns
- <literal>null</literal> if it doesn't exist. The old incorrect
- behavior could cause page rendering to fail if the method is
- called after the page is partially flushed. But beware, the old
- behavior has possibly hidden some bugs of the Web application,
- where it forgot to create the session, so with the new correct
- behavior you may face malfunction caused by previously cloaked
- bugs of the Web application. (It's the task of the MVC
- Controller to create the session, except if the JSP tag that
- needs a session is written so it creates it automatically, but
- then it doesn't expects that <literal>getSession()</literal>
- will do it.)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>BeansWrapper</literal> didn't always
- handled properly the case of a Java class having both a public
- static field and a public static method with the same
- name.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>SimpleMethodModel</literal> had
- incorrectly propagate exceptions sometimes, causing null pointer
- exception.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The template execution may used outdated cached
- values when you have processed the same
- <literal>Environment</literal> for multiple times, and changed
- settings between the two processings. Note that this could
- happen only in single-thread environment, where such setting
- modifications are allowed.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Some of the string built-ins has died with
- <literal>IndexOutOfBounds</literal> exception if the template
- author has forgotten to specify required parameters. Now they
- die with more helpful error messages.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.ext.dom.NodeModel.equals(...)</literal> has
- died with null pointer exception if its argument was
- <literal>null</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The cause exception of
- <literal>TemplateException</literal>-s was sometimes printed
- twice in stack traces with J2SE 1.4 or later.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The
- <literal>StringUtil.FTLStringLiteralEnc(String)</literal> method
- was finished.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixes and improvements in the Manual and in the API
- JavaDoc.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>The history of the releases before the final version</title>
-
- <section>
- <title>Differences between the preview release and final
- release</title>
-
- <itemizedlist>
- <listitem>
- <para>Added a dirty hack that prints
- <literal>ServletException</literal> root cause in
- <literal>TemplateException</literal>'s stack trace if that's
- the direct cause exception of the
- <literal>TemplateException</literal>, despite the poorly
- written <literal>ServletException</literal> class.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.ext.dom.NodeModel.equals(...)</literal>
- has died with null pointer exception if its argument was
- <literal>null</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The cause exception of
- <literal>TemplateException</literal>-s was sometimes printed
- twice in stack traces with J2SE 1.4 or later.</para>
- </listitem>
-
- <listitem>
- <para>More minor improvements in the Manual.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
- </section>
-
- <section xml:id="versions_2_3">
- <title>2.3</title>
-
- <para>Date of release: 2004-June-15</para>
-
- <para>FreeMarker 2.3 introduces numerous little new features and
- quality improvements compared to the 2.2.x series. The most notable
- improvements are the ability to define functions (methods) in
- templates, the ability to interpolate variables in string literals,
- the support for a variable number of macro parameters, and the more
- intelligent default object wrapper. Although none of the improvements
- is a drastic change, the 2.3.x series is not backward compatible with
- the 2.2.x series (see the list below), so you may choose to use it for
- new projects only.</para>
-
- <para>Probably the most ``loudly promoted'' new feature is the totally
- redesigned XML wrapper. With the new XML wrapper FreeMarker targets a
- new application domain, which is similar to the application domain of
- XSLT: transforming complex XML to whatever textual output. Although
- this subproject is young, it is definitely usable in practice. See the
- <link linkend="xgui">XML Processing Guide</link> for more
- details.</para>
-
- <section>
- <title>Non backward-compatible changes!</title>
-
- <itemizedlist>
- <listitem>
- <para>Since interpolations (<literal>${...}</literal> and
- <literal>#{...}</literal>) now work inside string literals, the
- character sequence <literal>${</literal> and
- <literal>#{</literal> in string literals are reserved for that.
- So if you have something like <literal><#set x =
- "${foo}"></literal>, then you have to replace it with
- <literal><#set x = r"${foo}"></literal> -- beware, escapes
- such as <literal>\n</literal> will not work in raw
- (<literal>r</literal>) strings.</para>
- </listitem>
-
- <listitem>
- <para>The default (initial) value of the
- <literal>strict_syntax</literal> setting has been changed from
- <literal>false</literal> to <literal>true</literal>. When
- <literal>strict_syntax</literal> is <literal>true</literal>,
- tags with old syntax as <literal><include
- "foo.ftl"></literal> will be considered as static text (so
- they go to the output as-is, like HTML tags do), and not as FTL
- tags. Such tags have to be rewritten to <literal><#include
- "foo.ftl"></literal>, since only parts that starts with
- <literal><#</literal>, <literal></#</literal>,
- <literal><@</literal>, or <literal></@</literal> count as
- FTL tags. Or, to recover the old transitional behavior, where
- both legacy and new tag syntax was recognized, you have to
- explicitly set <literal>strict_syntax</literal> to
- <literal>false</literal>:
- <literal>cfg.setStrictSyntaxMode(false)</literal>. Also, for
- individual templates you can force the old behavior by starting
- the template with <literal><#ftl
- strict_syntax=false></literal>. (For more information about
- why strict syntax is better than old syntax <link
- linkend="ref_depr_oldsyntax">read this...</link>)</para>
- </listitem>
-
- <listitem>
- <para>Several classes were moved from the
- <literal>freemarker.template</literal> package, to the new
- <literal>freemarker.core</literal> package:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>"Normal" classes: <literal>ArithmeticEngine</literal>,
- <literal>Configurable</literal>,
- <emphasis><literal>Environment</literal></emphasis></para>
- </listitem>
-
- <listitem>
- <para>Exceptions:
- <literal>InvalidReferenceException</literal>,
- <literal>NonBooleanException</literal>,
- <literal>NonNumericalException</literal>,
- <literal>NonStringException</literal>,
- <literal>ParseException</literal>,
- <literal>StopException</literal></para>
- </listitem>
-
- <listitem>
- <para>Errors: <literal>TokenMgrError</literal></para>
- </listitem>
- </itemizedlist>
-
- <para>The main reason of the splitting of
- <literal>freemarker.template</literal> package was that the
- amount of "expert" public classes and interfaces grows too much,
- as we introduce API-s for third-party tools, such as debugging
- API.</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.TemplateMethodModel.exec</literal>
- now returns <literal>Object</literal> instead of
- <literal>TemplateModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>White-space stripping is now more aggressive as before: it
- always removes leading and trailing white-space if the line only
- contains FTL tags. (Earlier the white-space was not removed if
- the tag was <literal><#include
- <replaceable>...</replaceable>></literal> or user-defined
- directive tag with empty directive syntax as
- <literal><@myMacro/></literal> (or its equivalents:
- <literal><@myMacro></@myMacro></literal> and
- <literal><@myMacro></@></literal>). Now white-space
- is removed in these cases as well.) Also, white-space sandwiched
- between two non-outputting elements, such as macro definitions,
- assignments, imports, or property settings, is now ignored. More
- information: <xref
- linkend="dgui_misc_whitespace_stripping"/></para>
- </listitem>
-
- <listitem>
- <para>The <literal>function</literal> directive is now used for
- defining methods. You should replace <literal>function</literal>
- with <literal>macro</literal> in your old templates. Note,
- however, that old <literal>function</literal>-s will still work
- if you don't use the <literal>return</literal> directive in
- them, and you invoke them with the deprecated the
- <literal>call</literal> directive.</para>
- </listitem>
-
- <listitem>
- <para>The expressions <literal>as</literal>,
- <literal>in</literal>, and <literal>using</literal> are now
- keywords in the template language and cannot be used as
- top-level variable names without square-bracket syntax. If, by
- some chance, you have top-level variables that use one of these
- names, you will have to rename them, or use the square-bracket
- syntax with the <literal>.vars</literal> special variable:
- <literal>.vars["in"]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>?new</literal> built-in, as it was
- implemented, was a security hole. Now, it only allows you to
- instantiate a java object that implements the
- <literal>freemarker.template.TemplateModel</literal> interface.
- If you want the functionality of the <literal>?new</literal>
- built-in as it existed in prior versions, make available an
- instance of the
- <literal>freemarker.template.utility.ObjectConstructor</literal>
- class to your template. (For example:
- <literal>myDataModel.put("objConstructor", new
- ObjectConstructor());</literal>, and then in the template you
- can do this: <literal><#assign aList =
- objConstructor("java.util.ArrayList", 100)></literal>)</para>
- </listitem>
-
- <listitem>
- <para>Changes to the
- <literal>FreemarkerServlet</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>FreemarkerServlet</literal> uses
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> by default
- instead of <literal>ObjectWrapper.BEANS_WRAPPER</literal>.
- What this means is that, by default, objects of type
- <literal>java.lang.String</literal>,
- <literal>java.lang.Number</literal>,
- <literal>java.util.List</literal>, and
- <literal>java.util.Map</literal> will be wrapped as
- <literal>TemplateModels</literal> via the classes
- <literal>SimpleScalar</literal>,
- <literal>SimpleNumber</literal>,
- <literal>SimpleSequence</literal>, and
- <literal>SimpleHash</literal> respectively. Thus, the java
- methods on those objects will not be available. The default
- wrapper implementation in FreeMarker 2.3 automatically knows
- how to wrap Jython objects, and also wraps
- <literal>org.w3c.dom.Node</literal> objects into instances
- of <literal>freemarker.ext.dom.NodeModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>FreemarkerServlet</literal> base
- implementation no longer deduces the locale used for
- templates with <literal>HttpRequest.getLocale()</literal>.
- Rather, it simply delegates to the new protected method,
- <literal>deduceLocale</literal>. The default implementation
- of this method simply returns the value of configuration the
- <literal>locale</literal> setting.</para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Interpolation in string literals. For convenience,
- interpolations are now supported in string literals. For
- example: <literal><@message "Hello ${user}!" /></literal>
- is the same as <literal><@message "Hello " + user + "!"
- /></literal></para>
- </listitem>
-
- <listitem>
- <para>Raw string literals: In string literals prefixed with
- <literal>r</literal>, interpolations and escape sequences will
- not be interpreted as special tokens. For example:
- <literal>r"\n${x}"</literal> will be simply interpreted as the
- character sequence <literal>'\'</literal>,
- <literal>'n'</literal>, <literal>'$'</literal>,
- <literal>'{'</literal>, <literal>'x'</literal>,
- <literal>'}'</literal>, and not as line-feed and the value of
- the <literal>x</literal> variable.</para>
- </listitem>
-
- <listitem>
- <para>Method variables can be defined in FTL, with the <link
- linkend="ref.directive.function"><literal>function</literal></link>
- directive.</para>
- </listitem>
-
- <listitem>
- <para>Support for a variable number of macro parameters. If the
- last parameter in a macro declaration ends with
- <literal>...</literal>, all extra parameters passed to the macro
- will be available via that parameter. For macros called with
- positional parameters, the parameter will be a sequence. For
- named parameters, the parameter will be a hash. Note that it all
- works with the new <literal>function</literal> directive as
- well.</para>
- </listitem>
-
- <listitem>
- <para>A new header parameter, <literal>strip_text</literal>,
- that removes all top-level text from a template. This is useful
- for ``include files'' to suppress newlines that separate the
- macro definitions. See <link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- directive</link></para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_specvar">special variable</link>:
- <literal>.vars</literal>. This is useful to read top-level
- variables with square bracket syntax, for example
- <literal>.vars["name-with-hyphens"]</literal> and
- <literal>.vars[dynamicName]</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>macro</literal> and assignment directives now
- accept arbitrary destination variable name with quoted syntax.
- For example: <literal><#macro
- "name-with-hyphens"><replaceable>...</replaceable></literal>
- or <literal><#assign "foo bar" = 123></literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>?keys</literal> and
- <literal>?values</literal> hash built-ins now return sequences.
- In practical terms this means you can access their sizes or
- retrieve their sub variables by index, and use all of the <link
- linkend="ref_builtins_sequence">sequence built-ins</link>. (Note
- for the programmers: The <literal>TemplateHashModelEx</literal>
- interface has not been changed. Your old code will work. See the
- API documentation to see why.)</para>
- </listitem>
-
- <listitem>
- <para>Existence built-ins (<literal>?default</literal>,
- <literal>?exists</literal>, etc.) are now working with sequence
- sub variables as well. Read the documentation of the
- <literal>default</literal> built-in for more information.</para>
- </listitem>
-
- <listitem>
- <para>White-space stripping is now more aggressive as before: it
- always removes leading and trailing white-space if the line only
- contains FTL tags. (Earlier the white-space was not removed if
- the tag was <literal><#include
- <replaceable>...</replaceable>></literal> or user-defined
- directive tag with empty directive syntax as
- <literal><@myMacro/></literal> (or its equivalents:
- <literal><@myMacro></@myMacro></literal> and
- <literal><@myMacro></@></literal>). Now white-space
- is removed in these cases as well.) Also, top-level white-space
- that separates macro definitions and/or assignments is now
- ignored. More information: <xref
- linkend="dgui_misc_whitespace_stripping"/></para>
- </listitem>
-
- <listitem>
- <para>White-space stripping can be disabled for a single line
- with the <link
- linkend="ref.directive.nt"><literal>nt</literal></link>
- directive (for No Trim).</para>
- </listitem>
-
- <listitem>
- <para>Hashes can be concatenated using the <literal>+</literal>
- operator. The keys in the hash on the right-hand side take
- precedence.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for Java and JavaScript string escaping:
- <link linkend="ref_builtin_j_string">j_string</link> and <link
- linkend="ref_builtin_js_string">js_string</link></para>
- </listitem>
-
- <listitem>
- <para>The <literal>replace</literal> and
- <literal>split</literal> built-ins now support case-insensitive
- comparsion and regular expressions (J2SE 1.4+ only), and some
- other new options. More information can be found <link
- linkend="ref_builtin_string_flags">here</link>.</para>
- </listitem>
-
- <listitem>
- <para>New built-in for regular expression matching (J2SE 1.4+
- only): <link
- linkend="ref_builtin_matches"><literal>matches</literal></link></para>
- </listitem>
-
- <listitem>
- <para>New built-in, <literal>eval</literal>, to evaluate a
- string as FTL expression. For example
- <literal>"1+2"?eval</literal> returns the number 3.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for Java and JavaScript string escaping:
- <link linkend="ref_builtin_j_string">j_string</link> and <link
- linkend="ref_builtin_js_string">js_string</link></para>
- </listitem>
-
- <listitem>
- <para>New special variables to read the value of the locale
- setting: <literal>locale</literal>, <literal>lang</literal>. See
- more <link linkend="ref_specvar">in the
- reference...</link></para>
- </listitem>
-
- <listitem>
- <para>New special variable to read the FreeMarker version
- number: <literal>version</literal>. See more <link
- linkend="ref_specvar">in the reference...</link></para>
- </listitem>
-
- <listitem>
- <para>Tree new directives, <literal>recurse</literal>,
- <literal>visit</literal> and <literal>fallback</literal>, were
- introduced to support declarative node-tree processing. These
- are meant to be used typically (though not exclusively) for
- processing XML input. Together with this, a new variable type
- has been introduced, the node type. See the <link
- linkend="xgui_declarative">chapter on declarative XML
- processing</link> for more details.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>?new</literal> built-in, as it was
- implemented, was a security hole. Now, it only allows you to
- instantiate a java object that implements the
- <literal>freemarker.template.TemplateModel</literal> interface.
- <phrase role="forProgrammers">If you want the functionality of
- the <literal>?new</literal> built-in as it existed in prior
- versions, make available an instance of the
- <literal>freemarker.template.utility.ObjectConstructor</literal>
- class to your template. (For example:
- <literal>myDataModel.put("objConstructor", new
- ObjectConstructor());</literal>, and then in the template you
- can do this: <literal><#assign aList =
- objConstructor("java.util.ArrayList",
- 100)></literal>)</phrase></para>
- </listitem>
-
- <listitem>
- <para>Variable names can contain <literal>@</literal> anywhere
- (without using quote-bracket syntax). For example:
- <literal><#assign x@@@ = 123></literal> is valid.</para>
- </listitem>
-
- <listitem>
- <para>The expressions <literal>as</literal>,
- <literal>in</literal>, and <literal>using</literal> are now
- keywords in the template language and cannot be used as
- top-level variable names without square-bracket syntax (as
- <literal>.vars["in"]</literal>).</para>
- </listitem>
-
- <listitem>
- <para>New parameter to the <link
- linkend="ref_directive_ftl"><literal>ftl</literal>
- directive</link>: <literal>attributes</literal>. The value of
- this attribute is a hash that associates arbitrary attributes
- (name-value pairs) to the template. The values of the attributes
- can be of any type (string, number, sequence... etc.).
- FreeMarker doesn't try to understand the meaning of the
- attributes. It's up to the application that encapsulates
- FreeMarker (as a Web application framework). Thus, the set of
- allowed attributes and their semantic is application (Web
- application framework) dependent.</para>
- </listitem>
-
- <listitem>
- <para>Other minor quality improvements...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Smarter default object wrapping: The default object
- wrapper is now
- <literal>freemarker.template.DefaultObjectWrapper</literal>,
- which falls back on wrapping arbitrary objects as beans using
- the <literal>freemarker.ext.beans.BeansWrapper</literal>. Also,
- it will wrap <literal>org.w3c.dom.Node</literal> objects with
- the new DOM wrapper. Also, it is aware of Jython objects, and
- will use <literal>freemarker.ext.jython.JythonWrapper</literal>
- if the object passed in is a Jython object. (We count it as a
- backward compatible change, since this new object wrapper wraps
- differently only those objects that the old wrapper was not able
- to wrap, so it has thrown exception.)</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.TemplateMethodModel.exec</literal>
- now returns <literal>Object</literal> instead of
- <literal>TemplateModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The default (initial) value of the
- <literal>strict_syntax</literal> setting has been changed from
- <literal>false</literal> to <literal>true</literal>. When
- <literal>strict_syntax</literal> is <literal>true</literal>,
- tags with old syntax as <literal><include
- "foo.ftl"></literal> will be considered as static text (so
- they go to the output as-is, like HTML tags do), and not as FTL
- tags. Such tags have to be rewritten to <literal><#include
- "foo.ftl"></literal>, since only parts that starts with
- <literal><#</literal>, <literal></#</literal>,
- <literal><@</literal>, or <literal></@</literal> count as
- FTL tags. Or, to recover the old transitional behavior, where
- both legacy and new tag syntax was recognized, you have to
- explicitly set <literal>strict_syntax</literal> to
- <literal>false</literal>:
- <literal>cfg.setStrictSyntaxMode(false)</literal>. Also, for
- individual templates you can force the old behavior by starting
- the template with <literal><#ftl
- strict_syntax=false></literal>. (For more information about
- why strict syntax is better than old syntax <link
- linkend="ref_depr_oldsyntax">read this...</link>)</para>
- </listitem>
-
- <listitem>
- <para>New <literal>CacheStorage</literal> implementation:
- <literal>freemarker.cache.MruCacheStorage</literal>. This cache
- storage implements a two-level Most Recently Used cache. In the
- first level, items are strongly referenced up to the specified
- maximum. When the maximum is exceeded, the least recently used
- item is moved into the second level cache, where they are softly
- referenced, up to another specified maximum.
- <literal>freemarker.cache.SoftCachseStorage</literal> and
- <literal>StrongCachseStorage</literal> are deprected,
- <literal>MruCachseStorage</literal> is used everywhere instead.
- The default cache storage is now an
- <literal>MruCachseStorage</literal> object with 0 strong size,
- and infinite soft size.
- <literal>Configuration.setSetting</literal> for
- <literal>cache_storage</literal> now understands string values
- as <literal>"strong:200, soft:2000"</literal>.</para>
- </listitem>
-
- <listitem>
- <para>For <literal>BeansWrapper</literal> generated models, you
- can now use the <literal>${obj.method(args)}</literal> syntax to
- invoke methods whose return type is <literal>void</literal>.
- <literal>void</literal> methods now return
- <literal>TemplateModel.NOTHING</literal> as their return
- value.</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.SimpleHash</literal> now can
- wrap read-only <literal>Map</literal>-s, such as the map of HTTP
- request parameters in Servlet API.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>TemplateNodeModel</literal> interface was
- introduced to support recursive processing of trees of nodes.
- Typically, this will be used in relation to XML.</para>
- </listitem>
-
- <listitem>
- <para>New package: <literal>freemarker.ext.dom</literal>. This
- contains the new XML wrapper, that supports the processing of
- XML documents using the visitor pattern (i.e. with
- <literal><#visit <replaceable>...</replaceable>></literal>
- and similar directives), and to provide more convenient XML
- traversing as the legacy wrapper. See the <link
- linkend="xgui">XML processing guide</link> for more
- details.</para>
- </listitem>
-
- <listitem>
- <para>New package: <literal>freemarker.core</literal>. Classes
- used by mostly power-users was moved here from the
- <literal>freemarker.template</literal> package. The main reason
- of the splitting of <literal>freemarker.template</literal>
- package was that the amount of "expert" public classes and
- interfaces grows too much, as we introduce API-s for third-party
- tools, such as debugging API.</para>
- </listitem>
-
- <listitem>
- <para>New package: <literal>freemarker.debug</literal>. This
- provides a debugging API, by which you can debug executing
- templates through network (RMI). You have to write the front-end
- (client), as the API is just the server side. For more
- information please read the JavaDoc of the
- <literal>freemarker.debug</literal> package.</para>
- </listitem>
-
- <listitem>
- <para>You can query the FreeMarker version number with static
- method <literal>Configuration.getVersionNumber()</literal>.
- Also, the <literal>Manifest.mf</literal> included in
- <literal>freemarker.jar</literal> now contains the FreeMarker
- version number, furthermore, executing it with <literal>java
- -jar freemarker.jar</literal> will print the version number to
- the stdout.</para>
- </listitem>
-
- <listitem>
- <para>Added a new protected <literal>FreemarkerServlet</literal>
- method: <literal>Configuration
- getConfiguration()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Date support is now labeled as final. (It was experimental
- earlier.)</para>
- </listitem>
-
- <listitem>
- <para>The <literal>BeansWrapper</literal> has been improved to
- prevent some security exceptions when introspecting.</para>
- </listitem>
-
- <listitem>
- <para>Other minor quality improvements and extensions...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixes and improvements in the Manual and in the API
- JavaDoc.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>The history of the releases before the final version</title>
-
- <section>
- <title>Differences between the final release and Release Candidate
- 4</title>
-
- <itemizedlist>
- <listitem>
- <para>Added a new special variable to print the FreeMarker
- version number: <literal>version</literal>. See more <link
- linkend="ref_specvar">in the reference...</link></para>
- </listitem>
-
- <listitem>
- <para>Minor documentation fixes and improvements.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Release Candidate 4 and Release
- Candidate 3</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>BeansWrapper</literal> has been improved to
- prevent some security exceptions when introspecting.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>FreemarkerXmlTask</literal> has two new
- sub-tasks that can be used to prepare template execution with
- Jython scripts: <literal>prepareModel</literal> and
- <literal>prepareEnvironment</literal>. The
- <literal>jython</literal> sub-task is now deprecated, and does
- the same as <literal>prepareEnvironment</literal>. See the
- Java API documentation for more details.</para>
- </listitem>
-
- <listitem>
- <para>New special variable to read the FreeMarker version
- number: <literal>version</literal>. See more <link
- linkend="ref_specvar">in the reference...</link></para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Greater-than sign doesn't confuse the
- <literal>eval</literal> built-in anymore.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>BeansWrapper</literal> now wrapps
- the <literal>null</literal> return values of methods
- appropriately.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>FreemarkerXmlTask</literal> doesn't
- need Jython classes anymore, unless you really use Jython
- scripts. Several other bugfixes in the Jython related
- features.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: If the template exception handler has ignored
- the exception, errors occurring in interpolations inside FTL
- tags (e.g. <literal><#if "foo${badVar}" !=
- "foobar"></literal>) were handled in the same way as errors
- occuring in interpolations outside FTL tags. Thus, the
- directive call was not skipped, and the problematic
- interpolation was replaced with an empty string. (This was
- inconsistent with the behavior of <literal><#if
- "foo"+badVar != "foobar"></literal>, which should be 100%
- equivalent with the previous example.)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>FileTemplateLoader</literal> is now
- more robust when it receives paths that are malformed
- according the native file system. In the earlier version such
- paths sometimes caused unexpected
- <literal>IOException</literal> that aborted the searching for
- the template in further
- <literal>FileTemplateLoader</literal>-s when you use the
- <literal>MultiTemplateLoader</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Release Candidate 3 and Release
- Candidate 2</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: Fixing a fatal bug in the template cache that
- was introduced with the latest cache ``bugfix''. The template
- cache has always reloaded the unchanged template when the
- update delay has been elapsed, until the template has been
- actually changed, in which case it has never reloaded the
- template anymore.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Release Candidate 2 and Release
- Candidate 1</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: The template cache didn't reload the template
- when it was replaced with an older version.</para>
- </listitem>
-
- <listitem>
- <para>API JavaDoc fix: date/time related classes/interfaces
- were marked as experimental. They are not experimental.</para>
- </listitem>
-
- <listitem>
- <para>Minor site improvements.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Release Candidate 1 and Preview 16
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para><emphasis>Warning! Non-backward-compatible
- change!</emphasis> The default (initial) value of the
- <literal>strict_syntax</literal> setting has been changed from
- <literal>false</literal> to <literal>true</literal>. When
- <literal>strict_syntax</literal> is <literal>true</literal>,
- tags with old syntax as <literal><include
- "foo.ftl"></literal> will be considered as static text (so
- they go to the output as-is, like HTML tags do), and not as
- FTL tags. Such tags have to be rewritten to
- <literal><#include "foo.ftl"></literal>, since only
- parts that starts with <literal><#</literal>,
- <literal></#</literal>, <literal><@</literal>, or
- <literal></@</literal> count as FTL tags. Or, to recover
- the old transitional behavior, where both legacy and new tag
- syntax was recognized, you have to explicitly set
- <literal>strict_syntax</literal> to <literal>false</literal>:
- <literal>cfg.setStrictSyntaxMode(false)</literal>. Also, for
- individual templates you can force the old behavior by
- starting the template with <literal><#ftl
- strict_syntax=false></literal>. (For more information about
- why strict syntax is better than old syntax <link
- linkend="ref_depr_oldsyntax">read this...</link>)</para>
- </listitem>
-
- <listitem>
- <para>New parameter to the <link
- linkend="ref_directive_ftl"><literal>ftl</literal>
- directive</link>: <literal>attributes</literal>. The value of
- this attribute is a hash that associates arbitrary attributes
- (name-value pairs) to the template. The values of the
- attributes can be of any type (string, number, sequence...
- etc.). FreeMarker doesn't try to understand the meaning of the
- attributes. It's up to the application that encapsulates
- FreeMarker (as a Web application framework). Thus, the set of
- allowed attributes and their semantic is application (Web
- application framework) dependent.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.template.utility.DeepUnwrap</literal>
- unwrapped sequences to empty
- <literal>ArrayList</literal>-s.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: If you included/imported a template with
- <literal>*/</literal> in path (acquisition), and that template
- in turn itself included/imported another template with
- <literal>*/</literal> in path, it may failed.</para>
- </listitem>
-
- <listitem>
- <para>New methods to the
- <literal>freemarker.core.Environment</literal>:
- <literal>importLib(Template loadedTemplate, java.lang.String
- namespace)</literal>,
- <literal>getTemplateForImporting(...)</literal>,
- <literal>getTemplateForInclusion(...)</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Improvements in the
- <literal>java.io.IOException</literal> related error messages
- of the <literal>include</literal> and
- <literal>import</literal> directives.</para>
- </listitem>
-
- <listitem>
- <para>Minor improvements in the documentation.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 16 and Preview 15
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>New package: <literal>freemarker.debug</literal>. This
- provides a debugging API, by which you can debug executing
- templates through network (RMI). You have to write the
- front-end (client), as the API is just the server side. For
- more information please read the JavaDoc of the
- <literal>freemarker.debug</literal> package. (The debugging
- API is present for a while, just I forgot to announce it in
- the version history. Sorry for that.)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: With the new XML wrapper,
- <literal>@@markup</literal> and similar special keys:</para>
-
- <itemizedlist>
- <listitem>
- <para>have returned
- <literal><foo></foo></literal> for empty
- elements instead of <literal><foo /></literal>.
- Other than it was needlessly verbose, it has confused
- browsers if you generate HTML.</para>
- </listitem>
-
- <listitem>
- <para>have showed the attributes that have no explicitly
- given value in the original document, just a default value
- coming form the DTD.</para>
- </listitem>
-
- <listitem>
- <para>have forgot to put space before the system
- identifier in the <literal><!DOCTYPE
- <replaceable>...</replaceable>></literal>.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bugfix: XPath with Jaxen has died with
- <literal>NullPointerException</literal> if the context was an
- empty node set.</para>
- </listitem>
-
- <listitem>
- <para>A bit more intelligent Xalan XPath error
- messages.</para>
- </listitem>
-
- <listitem>
- <para>Revoked fallback-to-classloader logic from the template
- cache.</para>
- </listitem>
-
- <listitem>
- <para>From now, if no XPath engine is available, and the hash
- key in an ``XML query'' can't be interpreted without XPath, an
- error will tell this clearly, rather than silently returning
- undefined variable (null).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Some templates have caused the parser to
- die.</para>
- </listitem>
-
- <listitem>
- <para>Some other minor improvements here and there...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 15 and Preview 14
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: The new default template cache storage
- (<literal>MruCacheStorage</literal>) has started to
- continually fail with <literal>NullPointerException</literal>
- from a random point of time, usually when the memory usage was
- high in the JVM.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: In error messages, when the quoted FTL directive
- had nested content, that was quoted as well, so the quotation
- could be very long and expose nested lines needlessly.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 14 and Preview 13
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>freemarker.template.TemplateMethodModel.exec</literal>
- now returns <literal>Object</literal> instead of
- <literal>TemplateModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Fixes and improvements for XPath with Jaxen (not Xalan).
- Non-node-set XPath expressions are now working. FreeMarker
- variables are accessible in XPath expressions with XPath
- variable references (e.g.
- <literal>doc["book/chapter[title=$currentTitle]"]</literal>).</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.cache.SoftCachseStorage</literal>
- and <literal>StrongCachseStorage</literal> is deprected. The
- more flexible <literal>MruCachseStorage</literal> is used
- instead everywhere. The default cache storage is now an
- <literal>MruCachseStorage</literal> object with 0 strong size,
- and infinite soft size.
- <literal>Configuration.setSetting</literal> for
- <literal>cache_storage</literal> now understands string values
- as <literal>"strong:200, soft:2000"</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.cache.MruCachseStorage</literal> has died
- with <literal>ClassCastException</literal> sometimes.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for Java and JavaScript string escaping:
- <link linkend="ref_builtin_j_string">j_string</link> and <link
- linkend="ref_builtin_js_string">js_string</link></para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.TemplateExceptionHandler.HTML_DEBUG_HANDLER</literal>
- now prints more HTML-context-proof messages.</para>
- </listitem>
-
- <listitem>
- <para>You can query the FreeMarker version number with static
- method <literal>Configuration.getVersionNumber()</literal>.
- Also, the <literal>Manifest.mf</literal> included in
- <literal>freemarker.jar</literal> now contains the FreeMarker
- version number, furthermore, executing it with <literal>java
- -jar freemarker.jar</literal> will print the version number to
- the stdout.</para>
- </listitem>
-
- <listitem>
- <para>Added a new protected
- <literal>FreemarkerServlet</literal> method:
- <literal>Configuration getConfiguration()</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: FreeMarker has frozen on empty conditional
- blocks in certain contexts.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Methods called twice on an object using the
- <literal>list</literal> directive, as
- <literal>parent.getChildren()</literal> with
- <literal><#list parent.children as child>
- ...</#list></literal></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 13 and Preview 12
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>White-space stripping is now more aggressive as before:
- it always removes leading and trailing white-space if the line
- only contains FTL tags. (Earlier the white-space was not
- removed if the tag was <literal><#include
- <replaceable>...</replaceable>></literal> or user-defined
- directive tag with empty directive syntax as
- <literal><@myMacro/></literal> (or its equivalents:
- <literal><@myMacro></@myMacro></literal> and
- <literal><@myMacro></@></literal>). Now
- white-space is removed in these cases as well.) Also,
- top-level white-space that separates macro definitions and/or
- assignments is now ignored. More information: <xref
- linkend="dgui_misc_whitespace_stripping"/></para>
- </listitem>
-
- <listitem>
- <para>White-space stripping can be disabled for a single line
- with the <link
- linkend="ref.directive.nt"><literal>nt</literal></link>
- directive (for No Trim).</para>
- </listitem>
-
- <listitem>
- <para>A new directive for the declarative XML processing:
- <link
- linkend="ref.directive.fallback"><literal>fallback</literal></link></para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.SimpleHash</literal> now
- can wrap read-only <literal>Map</literal>-s, such as the map
- of HTTP request parameters in Servlet API.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 12 and Preview 11
- releases</title>
-
- <para>The only change between this and the previous preview
- release is that Preview 11 had a bug where DOM trees would
- <emphasis>never</emphasis> be garbage-collected.</para>
- </section>
-
- <section>
- <title>Differences between the Preview 11 and Preview 10
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Many XML related changes. Some of them are incompatible
- with the previous preview releases! For a more detailed
- explanation of how XML related features now work, see: <xref
- linkend="xgui"/></para>
-
- <itemizedlist>
- <listitem>
- <para>Attention! Attribute queries such as
- <literal>foo.@bar</literal> now return sequences
- (similarly to child element queries and XPath queries),
- not single nodes. Because of the rule with node sequences
- of size 1, it is still good to write
- <literal>${foo.@bar}</literal>, but built-ins such as
- <literal>?exists</literal>, <literal>?if_exists</literal>
- or <literal>?default</literal> don't work as before. For
- example, instead of
- <literal>foo.@bar?default('black')</literal>, you now have
- to write <literal>foo.@bar[0]?default('black')</literal>.
- So if you have used existence built-ins with attributes,
- you have to find those occurrences in the templates and
- add that <literal>[0]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Attention! XML name-space handling has been totally
- reworked and is absolutely incompatible with pre 10. Don't
- worry about this if none of your XML input documents use
- you use <literal>xmlns</literal> attributes. Worry,
- though, if you have utilized the ``loose mode'', where
- only the local name of elements were compared, because
- that's now gone. Sorry...</para>
- </listitem>
-
- <listitem>
- <para>Attention! Special-keys <literal>@@</literal> and
- <literal>@*</literal> now return a sequence of attribute
- nodes instead of the hash of them.</para>
- </listitem>
-
- <listitem>
- <para>Several hash keys are now working for node sequences
- that store multiple nodes. For example, to get the list of
- all <literal>para</literal> elements of all
- <literal>chapter</literal>-s, just write
- <literal>doc.book.chapter.para</literal>. Or, to get list
- of title attributes of all <literal>chapter</literal>-s
- write <literal>doc.book.chapter.@title</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New special hash keys: <literal>**</literal>,
- <literal>@@start_tag</literal>,
- <literal>@@end_tag</literal>,
- <literal>@@attribute_markup</literal>,
- <literal>@@text</literal>,
- <literal>@@qname</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>?parent</literal> for attribute nodes now
- returns the element node the attribute node belongs
- to.</para>
- </listitem>
-
- <listitem>
- <para>You can use Jaxen instead of Xalan for XPath
- expressions, if you call the static
- <literal>freemarker.ext.dom.NodeModel.useJaxenXPathSupport()</literal>
- method once. We plan to use Jaxen automatically instead of
- Xalan if it is available, just the Jaxen support is not
- fully functional yet.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>New special variable: <literal>.vars</literal>. This is
- useful to read top-level variables with square bracket syntax,
- for example <literal>.vars["name-with-hyphens"]</literal> and
- <literal>.vars[dynamicName]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New built-in, <literal>eval</literal>, to evaluate a
- string as FTL expression. For example
- <literal>"1+2"?eval</literal> returns the number 3.</para>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> now uses the
- configuration's <literal>locale</literal> setting, rather than
- <literal>Locale.getDefault()</literal>, to set the locale of
- the templates. Also, the signature of the
- <literal>deduceLocale</literal> method has been
- changed.</para>
- </listitem>
-
- <listitem>
- <para>We have a new (beta status)
- <literal>CacheStorage</literal> implementation:
- <literal>freemarker.cache.MruCacheStorage</literal>. This
- cache storage implements a two-level Most Recently Used cache.
- In the first level, items are strongly referenced up to the
- specified maximum. When the maximum is exceeded, the least
- recently used item is moved into the second level cache, where
- they are softly referenced, up to another specified maximum.
- You can plug to try it with <literal>cfg.setCacheStorage(new
- freemarker.cache.MruCacheStorage(maxStrongSize,
- maxSoftSize))</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 10 and Preview 9
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>The special key <literal>@@xmlns</literal> was removed
- in favor of a new FTL directive for the same purpose,
- <literal><#xmlns...></literal>.</para>
- </listitem>
-
- <listitem>
- <para>By default, the system is stricter about the use of
- namespace prefixes. In general, you must use a prefix to
- qualify subelements that are associated with an XML
- nampespace. You can do this with the new
- <literal><#xmlns...></literal> directive, but prefixes
- declared in the input XML doc will actually work with no
- declaration.</para>
- </listitem>
-
- <listitem>
- <para>Introduced a new special key called
- <literal>@@text</literal> that returns all the text nodes
- contained (recursively) in an element all concatenated
- together.</para>
- </listitem>
-
- <listitem>
- <para>Either Jaxen or Xalan can be used to provide XPath
- functionality. Prior versions only worked with Xalan.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>FreemarkerServlet</literal> uses
- <literal>ObjectWrapper.DEFAULT_WRAPPER</literal> by default
- instead of <literal>ObjectWrapper.BEANS_WRAPPER</literal>.
- What this means is that, by default, objects of type
- <literal>java.lang.String</literal>,
- <literal>java.lang.Number</literal>,
- <literal>java.util.List</literal>, and
- <literal>java.util.Map</literal> will be wrapped as
- <literal>TemplateModels</literal> via the classes
- <literal>SimpleScalar</literal>,
- <literal>SimpleNumber</literal>,
- <literal>SimpleSequence</literal>, and
- <literal>SimpleHash</literal> respectively. Thus, the java
- methods on those objects will not be available. The default
- wrapper implementation in FreeMarker 2.3 automatically knows
- how to wrap Jython objects, and also wraps
- <literal>org.w3c.dom.Node</literal> objects into instances of
- <literal>freemarker.ext.dom.NodeModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>FreemarkerServlet</literal> base
- implementation no longer deduces the locale to use from the
- HttpRequest.getLocale() hook. Rather, it simply delegates to a
- <literal>deduceLocale()</literal> hook that is overridable in
- subclasses. The base implementation simply uses
- <literal>Locale.getDefault()</literal></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 9 and Preview 8
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixed bugs introduced with Preview 8: XPath,
- <literal>@@markup</literal> and
- <literal>@@nested_markup</literal> now works with the document
- node.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 8 and Preview 7
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>macro</literal> and assignment directives now
- accept arbitrary destination variable name with quoted syntax.
- For example: <literal><#macro
- "foo-bar"><replaceable>...</replaceable></literal> or
- <literal><#assign "this+that" = 123></literal>. This is
- important, because XML element names can contain hyphen, and
- it was not possible to define a handler macro for those
- elements, till now.</para>
- </listitem>
-
- <listitem>
- <para>Special key <literal>@@content</literal> was renamed to
- <literal>@@nested_markup</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Fixed outdated XML related Manual parts (that were
- outdated even in Preview 7).</para>
- </listitem>
-
- <listitem>
- <para>Better parse-error messages.</para>
- </listitem>
-
- <listitem>
- <para>Minor bugfixes here and there...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 7 and Preview 6
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Caching of XPath queries should lead to significant
- performance improvements for XML processing, at least when
- XPath is heavily used.</para>
- </listitem>
-
- <listitem>
- <para>Refinements in handling of XML namespaces in the XML
- processing functionality. The new
- <literal>strict_namespace_handling</literal> setting
- introduced in 2.3pre6 was removed. A general-purpose solution
- was arrived at that should make that configuration setting
- unnecessary.</para>
- </listitem>
-
- <listitem>
- <para>Special key <literal>@xmlns</literal> was renamed to
- @@xmlns. Reserved namespace prefix <literal>default</literal>
- was renamed to <literal>@@default</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>ftl</literal> directive now accepts
- non-string types.</para>
- </listitem>
-
- <listitem>
- <para>New special keys were introduced for XML node wrappers
- in the freemarker.ext.dom package. The
- <literal>@@markup</literal> key returns the literal markup
- that make up that element and the <literal>@@content</literal>
- key returns all the element's markup excluding the opening and
- closing tags.</para>
- </listitem>
-
- <listitem>
- <para>Minor bugfixes here and there...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 6 and Preview 5
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Existence built-ins (<literal>?default</literal>,
- <literal>?exists</literal>, etc.) now work with sequence sub
- variables as well. Read the <link
- linkend="ref.directive.default">documentation of the
- <literal>default</literal> built-in</link> for more
- information.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>matches</literal> built-in now returns a
- sequence instead of a collection.</para>
- </listitem>
-
- <listitem>
- <para>Refinements in handling of XML namespaces in the XML
- processing functionality. A new setting,
- <literal>strict_namespace_handling</literal> was introduced.
- If this is set (it is off by default) any node-handling macro
- used in with the visit/recurse machinery must be from a macro
- library that declares in its ftl header that it handles the
- namespace in question.</para>
- </listitem>
-
- <listitem>
- <para>Minor bugfixes here and there...</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 5 and Preview 4
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>replace</literal> and
- <literal>split</literal> built-ins now support
- case-insensitive comparison and regular expressions (J2SE 1.4+
- only), and some other new options. More information can be
- found <link
- linkend="ref_builtin_string_flags">here</link>.</para>
- </listitem>
-
- <listitem>
- <para>New butilt-in for regular expression matching (J2SE 1.4+
- only): <link
- linkend="ref_builtin_matches"><literal>matches</literal></link></para>
- </listitem>
-
- <listitem>
- <para>Minor bugfixes here and there...</para>
- </listitem>
-
- <listitem>
- <para>Manual: More browser-safe HTML-s. More updated
- content.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 4 and Preview 3
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: with multi-type variables, <literal>+</literal>
- operator overload for hash type had higher precedence than the
- precedence of some older overloads.</para>
- </listitem>
-
- <listitem>
- <para>The API documentation was missing from the distribution
- <literal>tar.gz</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 3 and Preview 2
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>XML processing: Many various bugfixes, especially with
- the declarative processing.</para>
- </listitem>
-
- <listitem>
- <para>XML processing: the <literal>namespace_uri</literal>
- built-in, the <literal>xmlnsuri</literal> header parameter,
- and the <literal>TemplateNodeModel.getNodeNamespace</literal>
- method were renamed to <literal>node_namespace</literal> and
- <literal>getNodeNamespace</literal> respectively.</para>
- </listitem>
-
- <listitem>
- <para>XML processing: Better documentation. Especially, note:
- <xref linkend="xgui"/></para>
- </listitem>
-
- <listitem>
- <para>A new header parameter, <literal>strip_text</literal>,
- that removes all top-level text from a template. See <link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- directive</link></para>
- </listitem>
-
- <listitem>
- <para>Support for a variable number of macro parameters. If
- the last parameter in a macro declaration ends with
- <literal>...</literal>, all extra parameters passed to the
- macro will be available via that parameter. For macros called
- with positional parameters, the parameter will be a sequence.
- For named parameters, the parameter will be a hash.</para>
- </listitem>
-
- <listitem>
- <para>For <literal>BeansWrapper</literal> generated models,
- you can now use the <literal>${obj.method(args)}</literal>
- syntax to invoke methods whose return type is
- <literal>void</literal>. <literal>void</literal> methods now
- return <literal>TemplateModel.NOTHING</literal> as their
- return value.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 2 and Preview 1
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>freemarker.ext.dom.NodeModel</literal> API
- changed slightly. The <literal>setDocumentBuilder()</literal>
- method was changed to
- <literal>setDocumentBuilderFactory()</literal> because the
- older scheme was not thread-safe. The
- <literal>stripComments</literal> and
- <literal>stripPIs</literal> methods are renamed to The
- <literal>removeComments</literal> and
- <literal>removePIs</literal>, and are fixed now. A new method,
- <literal>simplify</literal> has been added.</para>
- </listitem>
-
- <listitem>
- <para>The expressions <literal>as</literal>,
- <literal>in</literal>, and <literal>using</literal> are now
- keywords in the template language and cannot be used as
- top-level variable names without square-bracket syntax (as
- <literal>.vars["in"]</literal>). If, by some chance, you have
- top-level variables that use one of these names, you will have
- to rename them (or use the square-bracket syntax). Sorry for
- the inconvenience.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>?new</literal> built-in, as it was
- implemented, was a security hole. Now, it only allows you to
- instantiate a java object that implements the
- <literal>freemarker.template.TemplateModel</literal>
- interface. If you want the functionality of the
- <literal>?new</literal> built-in as it existed in prior
- versions, make available an instance of the new
- <literal>freemarker.template.utility.ObjectConstructor</literal>
- class to your template.</para>
- </listitem>
-
- <listitem>
- <para>The <literal><#recurse></literal> directive was
- broken. It did not work with a <literal>using</literal>
- clause. This is now fixed.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
- </section>
-
- <section xml:id="versions_2_2_8">
- <title>2.2.8</title>
-
- <para>Date of release: 2004-June-15</para>
-
- <para>Bugfix and maintenance release.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Added a new special variable to print the FreeMarker
- version number: <literal>version</literal>. See more <link
- linkend="ref_specvar">in the reference...</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>BeansWrapper</literal> has been improved to
- prevent some security exceptions when introspecting.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>FileTemplateLoader</literal> is now
- more robust when it receives paths that are malformed according
- the native file system. In the earlier version such paths
- sometimes caused unexpected <literal>IOException</literal> that
- aborted the searching for the template in further
- <literal>FileTemplateLoader</literal>-s when you use the
- <literal>MultiTemplateLoader</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Some parts of the FreeMarker code has been marked as
- privileged code section, so you can grant extra privileges to
- FreeMarker when you use a security manager (this is a
- backporting from 2.3). See more <link
- linkend="pgui_misc_secureenv">here...</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Minor documentation fixes and improvements.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_7">
- <title>2.2.7</title>
-
- <para>Date of release: 2004-March-17</para>
-
- <para>Important bugfix release.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: Fixing a fatal bug in the template cache that was
- introduced with the latest cache ``bugfix''. The template cache
- has always reloaded the unchanged template when the update delay
- has been elapsed, until the template has been actually changed,
- in which case it has never reloaded the template anymore.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_6">
- <title>2.2.6</title>
-
- <para>Date of release: 2004-March-13</para>
-
- <para>Maintenance and bugfix release. Some of improvements are
- back-portings from FreeMarker 2.3rc1.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New <link linkend="ref_specvar">special variable</link>:
- <literal>.vars</literal>. This is useful to read top-level
- variables with square bracket syntax, for example
- <literal>.vars["name-with-hyphens"]</literal> and
- <literal>.vars[dynamicName]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for Java and JavaScript string escaping:
- <link linkend="ref_builtin_j_string">j_string</link> and <link
- linkend="ref_builtin_js_string">js_string</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: The template cache didn't reload the template when
- it was replaced with an older version.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>freemarker.template.utility.DeepUnwrap</literal>
- unwrapped sequences to empty
- <literal>ArrayList</literal>-s.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: In error messages, when the quoted FTL directive
- had nested content, that was quoted as well, so the quotation
- could be very long and expose nested lines needlessly.</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.template.TemplateExceptionHandler.HTML_DEBUG_HANDLER</literal>
- now prints more HTML-context-proof messages.</para>
- </listitem>
-
- <listitem>
- <para>You can query the FreeMarker version number with static
- method <literal>Configuration.getVersionNumber()</literal>.
- Also, the <literal>Manifest.mf</literal> included in
- <literal>freemarker.jar</literal> now contains the FreeMarker
- version number, furthermore, executing it with <literal>java
- -jar freemarker.jar</literal> will print the version number to
- the stdout.</para>
- </listitem>
-
- <listitem>
- <para>Date support is now labeled as final. (It was experimental
- earlier.) There was no change since FreeMarker 2.2.1.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Fixes and improvements in the Manual and in the API
- JavaDoc. The documentation now works with the Eclipse help
- plugin (accessible in the ``Editor/IDE plugins'' section of the
- FreeMarker Web page).</para>
- </listitem>
-
- <listitem>
- <para>Minor site improvements.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_5">
- <title>2.2.5</title>
-
- <para>Date of release: 2003-09-19</para>
-
- <para>Maintenance and bugfix release.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Creating a <literal>Configuration</literal> instance using
- the default constructor no longer fails if the current directory
- is unreadable due to I/O problems, lack of security permissions,
- or any other exception.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_4">
- <title>2.2.4</title>
-
- <para>Date of release: 2003-09-03</para>
-
- <para>Maintenance and bugfix release.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Improvements to JSP taglib support. If some third party
- taglib didn't work for you with FreeMarker, maybe now it
- will.</para>
-
- <itemizedlist>
- <listitem>
- <para>The JSP <literal>PageContext</literal> now implements
- <literal>forward</literal> and <literal>include</literal>
- methods.</para>
- </listitem>
-
- <listitem>
- <para>Accepting <literal>EVAL_PAGE</literal> as an alias to
- <literal>SKIP_BODY</literal> in return values from
- <literal>doStartTag</literal>. It's a common bug in some
- widespread tag libraries, and now FreeMarker is less strict
- and accepts it.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Fixes for some rare problems regarding namespaces of
- macros.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Minor improvements to the documentation.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_3">
- <title>2.2.3</title>
-
- <para>Date of release: 2003-07-19</para>
-
- <para>Bugfix release.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Added the <literal>is_date</literal> built-in.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Various <literal>is_xxx</literal> built-ins were
- returning <literal>false</literal> when applied to undefined
- expressions. Now they correctly fail on them.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: The JSP taglib support can now read JSP 1.2
- compliant TLD XML files.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The JSP taglib support now emits more helpful
- exception messages when the specified TLD XML file is not found
- (previously it threw a
- <literal>NullPointerException</literal>).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The JSP taglib support now initializes a custom
- tag after its parent and page context is set as some tags expect
- them to be set when attribute setters are called.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The <literal>BeansWrapper</literal> could fail to
- analyze classes under very rare circumstances due to a premature
- storage optimization.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_2">
- <title>2.2.2</title>
-
- <para>Date of release: 2003-05-02</para>
-
- <para>Bugfix release.</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: The <literal>_text</literal> key of the
- <literal>freemarker.ext.xml.NodeListModel</literal> was not
- returning the text of the element when used with W3C DOM
- trees.</para>
- </listitem>
-
- <listitem>
- <para>The classes are now built against JDK 1.2.2 classes,
- ensuring the binary compatibility of FreeMarker distribution
- with JDK 1.2.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2_1">
- <title>2.2.1</title>
-
- <para>Date of release: 2003-04-11</para>
-
- <para>This version introduces important new features, such as the
- native FTL date/time type, and the auto-include and auto-import
- settings.</para>
-
- <para>The date/time support is experimental, but we hope it will not
- substantially change. We would like to label it as final ASAP, so we
- urge everybody to send feedback on this topic to the mailing
- lists.</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>New scalar type: date. For more information read: <xref
- linkend="dgui_datamodel_scalar"/>, <xref
- linkend="dgui_datamodel_scalar"/>, <link
- linkend="dgui_template_valueinserion_universal_date">interpolation</link>,
- <link linkend="ref_builtin_string_for_date">?string built-in for
- dates</link></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>New <literal>TemplateModel</literal> subinterface:
- <literal>TemplateDateModel</literal>. For more information read
- <xref linkend="pgui_datamodel_scalar"/></para>
- </listitem>
-
- <listitem>
- <para>auto-include and auto-import: With these new configuration
- level settings, you can include and import commonly used
- templates (usually collection of macro definitions) at the top
- of all templates, without actually typing <literal><#include
- <replaceable>...</replaceable>></literal> or
- <literal><#import
- <replaceable>...</replaceable>></literal> into the templates
- again and again. For more information please read the Java API
- documentation of <literal>Configuration</literal></para>
- </listitem>
-
- <listitem>
- <para>New template method:
- <literal>createProcessingEnvironment</literal>. This method
- makes it possible for you to do some special initialization on
- the <link
- linkend="gloss.environment"><literal>Environment</literal></link>
- before template processing, or to read the environment after
- template processing. For more information please read the Java
- API documentation.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>freemarker.ext.beans</literal>
- package: <literal>BeanModel</literal>,
- <literal>MapModel</literal>, and
- <literal>ResourceModel</literal> now implement
- <literal>TemplateHashModelEx</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix:
- <literal>Configurable.setSettings(Properties)</literal> didn't
- removed redundant spaces/tabs at the end of property
- values.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_2">
- <title>2.2</title>
-
- <para>Date of release: 2003-03-27</para>
-
- <para>This release introduces some really important new features.
- Unfortunately, evolution was painful again; we have a few non-backward
- compatible changes (see below). Also, for those of you awaiting
- desired native date/time type, sorry, it is still not here (because of
- some internal chaos in the team... stand by, it's coming).</para>
-
- <section>
- <title>Non backward-compatible changes!</title>
-
- <itemizedlist>
- <listitem>
- <para>Macros are now plain variables. This means that if you are
- unlucky and you have both a macro and another variable with the
- same name, now the variable will overwrite the macro, so your
- old template will malfunction. If you have a collection of
- common macros, you should use the new <link
- linkend="dgui_misc_namespace">namespace feature</link> to
- prevent accidental clashes with the variables used in the
- templates.</para>
- </listitem>
-
- <listitem>
- <para>With the introduction of the new <link
- linkend="dgui_misc_namespace">namespace support</link>,
- <literal>global</literal> and <literal>assign</literal>
- directives are no longer synonyms. <literal>assign</literal>
- creates a variable in the current <literal>namespace</literal>,
- while <literal>global</literal> creates variable that is visible
- from all namespaces (as if the variable would be in the
- data-model). Thus, the variable created with
- <literal>assign</literal> is more specific, and hides the
- variable of the same name created with
- <literal>global</literal>. As a result, if you use both
- <literal>global</literal> and <literal>assign</literal> mixed
- for the same variable in your templates, now they will
- malfunction. The solution is to search-and-replace all
- <literal>global</literal>s in your old templates with
- <literal>assign</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The reserved hash <literal>root</literal> no longer exists
- as a predefined variable (we no longer have reserved variables).
- Use <link linkend="dgui_template_exp_var_special">special
- variable expressions</link> to achieve similar effects. However,
- we have no equivalent replacement for <literal>root</literal>
- because of the changes in the variable scopes caused by the
- introduction of namespaces. You may should use
- <literal>.globals</literal> or
- <literal>.namespace</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>BeansWrapper</literal> no longer exposes
- native Java arrays, booleans, numbers, enumerations, iterators,
- and resource bundles as <literal>TemplateScalarModel</literal>.
- This way, number objects wrapped through
- <literal>BeansWrapper</literal> are subject to FreeMarker's
- number formatting machinery. Also, booleans can be formatted
- using the <literal>?string</literal> built-in.</para>
- </listitem>
-
- <listitem>
- <para>The signature of
- <literal>Configuration.setServletContextForTemplateLoading</literal>
- has been changed: the first parameter is now
- <literal>Object</literal> instead of
- <literal>javax.servlet.ServletContext</literal>. Thus, you have
- to recompile your classes that call this method. The change was
- required to prevent class-loading failure when
- <literal>javax.servlet</literal> classes are not available and
- you would not call this method.</para>
- </listitem>
-
- <listitem>
- <para>This release introduces a <link
- linkend="dgui_misc_whitespace">parse-time white-space
- remover</link> that strips some of the typical superfluous
- white-space around FreeMarker tags and comments. <emphasis>This
- feature is on by default!</emphasis> Most probably this will not
- cause problems if you generate white-space neutral output like
- HTML. But if it does cause undesirable reformatting in output
- you generate, you can disable it with
- <literal>config.setWhitespaceStripping(false)</literal>. Also,
- you can enable/disable it on a per-template basis with the new
- <link linkend="ref.directive.ftl"><literal>ftl</literal></link>
- directive.</para>
- </listitem>
-
- <listitem>
- <para>Some new directives were introduced:
- <literal>nested</literal>, <literal>import</literal>,
- <literal>escape</literal>, <literal>noescape</literal>,
- <literal>t</literal>, <literal>rt</literal>,
- <literal>lt</literal>. This means that if you are unlucky and
- the text of your template contains something like
- <literal><nested></literal>, then that will be
- misinterpreted as a directive. To prevent this kind of problem
- in the future, we recommend everybody to switch from the old
- syntax to the new syntax (``strict syntax''). The strict syntax
- will be the the default syntax starting from some of the later
- releases anyway. We plan to release a conversion tool for
- converting old templates. For more information please read:
- <xref linkend="ref_depr_oldsyntax"/></para>
- </listitem>
-
- <listitem>
- <para>The data-model created by the
- <literal>FreemarkerServlet</literal> now uses automatic scope
- discovery, so writing
- <literal>Application.<replaceable>attrName</replaceable></literal>,
- <literal>Session.<replaceable>attrName</replaceable></literal>,
- <literal>Request.<replaceable>attrName</replaceable></literal>
- is no longer mandatory; it's enough to write
- <literal><replaceable>attrName</replaceable></literal> (for more
- information <link linkend="topic.servlet.scopeAttr">read
- this</link>). This may break an old template if that rely on the
- non-existence of certain top-level variables.</para>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> now uses the encoding
- of the template file for the output, unless you specify the
- encoding in the <literal>ContentType</literal> init-param, such
- as <literal>text/html; charset=UTF-8</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The format of template paths is now more restricted than
- before. The path must not use <literal>/</literal>,
- <literal>./</literal> and <literal>../</literal> and
- <literal>://</literal> with other meaning as they have in URL
- paths (or in UN*X paths). The characters <literal>*</literal>
- and <literal>?</literal> are reserved. Also, the template loader
- must not want paths starting with <literal>/</literal>. For more
- information please read: <xref
- linkend="pgui_config_templateloading"/></para>
- </listitem>
-
- <listitem>
- <para>Till now
- <literal>TemplateTransformModel.getWriter</literal> has received
- null as parameter map if the transform was called without
- parameters. From now, it will receive an empty Map instead. Note
- that the previous API documentation didn't state that it always
- receives null if there are no parameters, so hopelessly only
- very few classes exploit this design mistake.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes in FTL (FreeMarker Template Language)</title>
-
- <itemizedlist>
- <listitem>
- <para>User-defined directives: Transform and macro call syntax
- has been unified; they can be called in the same way, as
- user-defined directives. This also means that macros support
- named parameters and nested content (like the -- now deprecated
- -- <literal>transform</literal> directive did). For example, if
- you have a macro called <literal>sect</literal>, you may call it
- via <literal><@sect title="Blah" style="modern">Blah
- blah...</@sect></literal>. For more information read:
- <xref linkend="dgui_misc_userdefdir"/></para>
- </listitem>
-
- <listitem>
- <para>Macros are now plain variables. This significantly
- simplifies FreeMarker semantics, while providing more
- flexibility; for example you can pass macros as parameters to
- other macros and transforms. As for the problem of clashing
- commonly-used-macro and variable names, we provide a more
- powerful solution: namespaces.</para>
- </listitem>
-
- <listitem>
- <para>Namespaces: Names-spaces are invaluable if you want to
- assemble collections (``libraries'') of macros and transforms
- (and other variables), and then use them in any template without
- worrying about accidental name clashes with the application
- specific and temporary variables, or with the variables of other
- collections you want to use in the same template. This is
- extremely important if FreeMarker users want to share their
- macro/transform collections. For more information read: <xref
- linkend="dgui_misc_namespace"/></para>
- </listitem>
-
- <listitem>
- <para>With the introduction of namespaces our variable related
- terminology changed. As a result, <literal>assign</literal> is
- no longer synonymous with <literal>global</literal>. The
- <literal>assign</literal> directive has been undeprecated, and
- should be used instead of <literal>global</literal> almost
- everywhere. In the new approach <literal>assign</literal>
- creates variables in the current namespace, while
- <literal>global</literal> creates a variable that is visible
- from all namespaces (as if the variable were in the root of the
- data-model). A variable created with <literal>assign</literal>
- in the current namespace hides the variable of the same name
- that was created with <literal>global</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>ftl</literal> directive: With this directive you
- can give information about the template for FreeMarker, like the
- encoding (charset) of the template, the used FTL syntax variant,
- etc. Also, this directive helps you to write templates that are
- less dependent on FreeMarker configuration settings, also it
- helps third-party tools to identify and correctly parse
- FreeMarker templates. For more information see: <link
- linkend="ref.directive.ftl"><literal>ftl</literal>
- directive</link></para>
- </listitem>
-
- <listitem>
- <para>White-space stripping: FreeMarker now automatically
- removes some of the typical superfluous white-spaces around
- FreeMarker tags and comments, like the indentation spaces
- before- and line-break after <literal><#if ...></literal>
- tags. For more information read: <xref
- linkend="dgui_misc_whitespace_stripping"/></para>
- </listitem>
-
- <listitem>
- <para>New directive to apply a common ("escaping") expression to
- all interpolations in a block: <link
- linkend="ref.directive.escape"><literal>escape</literal></link>.
- The name comes from the common usage of this directive for
- automatic HTML-escaping of interpolations.</para>
- </listitem>
-
- <listitem>
- <para>The new and preferred way of number formatting with
- <literal>string</literal> built-in is
- <literal>foo?string(format)</literal>, instead of the less
- natural <literal>foo?string[format]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>string</literal> built-in works for boolean
- values. For example: <literal>${spamFilter?string("enabled",
- "disabled")}</literal>. For more information <link
- linkend="ref_builtin_string_for_boolean">read the
- reference</link>.</para>
- </listitem>
-
- <listitem>
- <para>The default strings for outputting boolean value using the
- <literal>string</literal> built-in can be set using the
- <literal>boolean_format</literal> setting.</para>
- </listitem>
-
- <listitem>
- <para>Comments can be placed inside FTL tags and interpolations.
- For example: <literal><#assign <#-- a comment --> x =
- 3></literal></para>
- </listitem>
-
- <listitem>
- <para>All letters and numbers are enabled in variable names,
- also <literal>$</literal> is allowed (as in Java programming
- language). Thus you can use accents, Arabic letters, Chinese
- letters, etc.</para>
- </listitem>
-
- <listitem>
- <para>String literals can be quoted with apostrophe-quote.
- <literal>"foo"</literal> and <literal>'foo'</literal> are
- equivalent.</para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_builtins_string">string
- built-ins</link>: <literal>index_of</literal>,
- <literal>last_index_of</literal>,
- <literal>starts_with</literal>, <literal>ends_with</literal>,
- <literal>replace</literal>, <literal>split</literal>,
- <literal>chop_linebreak</literal>,
- <literal>uncap_first</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_builtins_sequence">sequence
- built-ins</link>: <literal>sort</literal>,
- <literal>sort_by</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New built-ins for experts to check the type of a variable.
- See: <link
- linkend="ref_builtin_isType"><literal>is_<replaceable>...</replaceable></literal>
- built-ins</link></para>
- </listitem>
-
- <listitem>
- <para>New built-in for experts to create a variable of certain
- Java <literal>TemplateModel</literal> implementation. See: <link
- linkend="ref_builtin_new"><literal>new</literal>
- built-in</link></para>
- </listitem>
-
- <listitem>
- <para>New built-in, <link
- linkend="ref_builtin_namespace"><literal>namespace</literal></link>,
- to get the namespace of a macro.</para>
- </listitem>
-
- <listitem>
- <para>New expression type: special variable expression. To
- prevent backward compatibility problems when we introduce new
- predefined variables, from now <link
- linkend="dgui_template_exp_var_special">special variable
- expressions</link> are used to access them.</para>
- </listitem>
-
- <listitem>
- <para>New directives: <literal>t</literal>,
- <literal>rt</literal> and <literal>lt</literal> directives allow
- you to do explicit white-space removal in extreme FTL
- applications. For more information read <link
- linkend="ref.directive.t">the reference</link>.</para>
- </listitem>
-
- <listitem>
- <para><literal>assign</literal>, <literal>local</literal> and
- <literal>global</literal> now can capture the output generated
- be the nested template fragment into the variable. This
- deprecates <literal>capture_output</literal> transform. More
- information: <link linkend="ref.directive.assign">assign
- directive reference</link></para>
- </listitem>
-
- <listitem>
- <para>Bulk assignments (as <literal><#assign x=1, y=2,
- z=3></literal>) no longer need colon to separate the
- assignments (as <literal><#assign x=1 y=2 z=3></literal>),
- although it is still allowed to preserve backward
- compatibility.</para>
- </listitem>
-
- <listitem>
- <para>Path that contains <literal>//:</literal> is considered as
- absolute path.</para>
- </listitem>
-
- <listitem>
- <para><literal>include</literal> and
- <literal>transform</literal> directives no longer need a
- semicolon to separate the template or transform name from the
- parameter list, although it is still allowed to preserve
- backward compatibility.</para>
- </listitem>
-
- <listitem>
- <para><literal>#</literal>-less tag syntax is deprecated (but
- still working). That is, you should write
- <literal><#<replaceable>directive
- ...</replaceable>></literal> instead of
- <literal><<replaceable>directive
- ...</replaceable>></literal>, and
- <literal></#<replaceable>directive
- ...</replaceable>></literal> instead of
- <literal></<replaceable>directive
- ...</replaceable>></literal>. For more info read: <xref
- linkend="ref_depr_oldsyntax"/></para>
- </listitem>
-
- <listitem>
- <para><literal>foreach</literal> is depreciated (but still
- working). Use <link
- linkend="ref.directive.list"><literal>list</literal></link>
- instead.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Undefined variables in hash and sequence
- constructors (as <literal>[a, b, c]</literal>) didn't caused
- errors.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: String concatenation had performance problem if
- there was multiple concatenations chained, as:
- <literal>"a"+x+"a"+x+"a"+x+"a"+x+"a"+x</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Arbitrary JSP custom tags can be used as FreeMarker
- transforms in <literal>FreemarkerServlet</literal>-driven
- templates. More information: <xref
- linkend="pgui_misc_servlet"/></para>
- </listitem>
-
- <listitem>
- <para>Various improvements for
- <literal>BeansWrapper</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>BeansWrapper</literal> no longer exposes
- arbitrary objects as
- <literal>TemplateScalarModel</literal>s, only
- <literal>java.lang.String</literal> and
- <literal>Character</literal> objects. This way, number
- objects wrapped through <literal>BeansWrapper</literal> are
- subject to FreeMarker's number formatting machinery. As a
- side effect, non-string and non-number objects that were
- previously accepted in equality and inequality operations
- (because they had a string representation) will now cause
- the engine to throw exception on comparison attempt.</para>
- </listitem>
-
- <listitem>
- <para><literal>java.lang.Character</literal> objects are
- exposed as scalars through
- <literal>BeansWrapper</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Experimental feature: With the
- <literal>setSimpleMapWrapper</literal> method of
- <literal>BeansWrapper</literal> you can configure it to wrap
- <literal>java.util.Map</literal>-s as
- <literal>TemplateHashModelEx</literal>-s, and do not expose
- the methods of the object.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para><literal>TransformControl</literal> interface (was
- experimental earlier): If the <literal>Writer</literal> returned
- by <literal>TemplateTransformModel.getWriter</literal>
- implements this interface, it can instruct the engine to skip or
- to repeat evaluation of the nested content, and gets notified
- about exceptions that are thrown during the nested content
- evaluation. Note that the <literal>onStart</literal> and
- <literal>afterBody</literal> methods now are allowed to throw
- <literal>IOException</literal>. For more information please read
- the API documentation.</para>
- </listitem>
-
- <listitem>
- <para>Localized lookup can be disabled with the new
- <literal>Configuration</literal> methods:
- <literal>set/getLocalizedLookup</literal>,
- <literal>clearTemplateCache</literal></para>
- </listitem>
-
- <listitem>
- <para>The new interface
- <literal>freemarker.cache.CacheStorage</literal> allows users to
- plug custom template caching strategies with the
- <literal>cache_storage</literal> setting. The core package now
- ships with two implementations:
- <literal>SoftCacheStorage</literal> and
- <literal>StrongCacheStorage</literal>. For more information
- read: <xref linkend="pgui_config_templateloading"/></para>
- </listitem>
-
- <listitem>
- <para>You can set settings with string name and string value
- with the new <literal>setSetting(String key, String
- value)</literal> method of <literal>Configurable</literal>
- super-classes (as <literal>Configuration</literal>). Also you
- can load settings from <literal>.properties</literal> file with
- the <literal>setSettings</literal> method.</para>
- </listitem>
-
- <listitem>
- <para>Other new <literal>Configuration</literal> methods:
- <literal>clearTemplateCache</literal>,
- <literal>clearSharedVariables</literal>,
- <literal>getTemplateLoader</literal>, and
- <literal>clone</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>TemplateTransformModel</literal>
- interface: <literal>getWriter</literal> can throw
- <literal>IOException</literal>, and can return
- <literal>null</literal> if the transform does not support body
- content.</para>
- </listitem>
-
- <listitem>
- <para>Till now
- <literal>TemplateTransformModel.getWriter</literal> has received
- null as parameter map if the transform was called without
- parameters. From now, it will receive an empty Map instead. Note
- that the previous API documentation didn't state that it always
- receives null if there are no parameters, so hopelessly only
- very few classes exploit this design mistake.</para>
- </listitem>
-
- <listitem>
- <para>Various improvements for
- <literal>FreemarkerServlet</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para>The data-model now uses automatic scope discovery, so
- writing
- <literal>Application.<replaceable>attrName</replaceable></literal>,
- <literal>Session.<replaceable>attrName</replaceable></literal>,
- <literal>Request.<replaceable>attrName</replaceable></literal>
- is no longer mandatory; it's enough to write
- <literal><replaceable>attrName</replaceable></literal>. For
- more information <link
- linkend="topic.servlet.scopeAttr">read this</link>.</para>
- </listitem>
-
- <listitem>
- <para><literal>FreemarkerServlet</literal> now uses the
- encoding of the template file for the output, unless you
- specify the encoding in the <literal>ContentType</literal>
- init-param, such as <literal>text/html;
- charset=UTF-8</literal>.</para>
- </listitem>
-
- <listitem>
- <para>All <literal>Configuration</literal> level settings
- can by set with Servlet init-params
- (<literal>template_exception_handler</literal>,
- <literal>locale</literal>, <literal>number_format</literal>,
- etc.).</para>
- </listitem>
-
- <listitem>
- <para>The object wrapper the servlet internally uses is now
- set as the default object wrapper for its
- <literal>Configuration</literal> instance.</para>
- </listitem>
-
- <listitem>
- <para>It no longer forces session creation for requests that
- don't belong to an existing session, improving
- scalability.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>JDOM independent XML-wrapping:
- <literal>freemarker.ext.xml.NodeListModel</literal> is a
- re-implementation of
- <literal>freemarker.ext.jdom.NodeListModel</literal> that does
- not rely on JDOM; you don't need JDOM .jar anymore. The new
- <literal>NodeListModel</literal> automatically uses W3C DOM,
- dom4j, or JDOM, depending on which library is available (that
- is, depending on what object do you pass to its
- constructor).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>WebappTemplateLoader</literal>: Template
- updating didn't worked correctly with Tomcat due the caching of
- resources. Now <literal>WebappTemplateLoader</literal> tries to
- access the resources directly as <literal>File</literal>, if it
- is possible, thus bypasses the caching.</para>
- </listitem>
-
- <listitem>
- <para>Various bug-fixes for
- <literal>FreemarkerServlet</literal>:</para>
-
- <itemizedlist>
- <listitem>
- <para>The servlet now loads the correct template if it was
- called through
- <literal>RequestDispatcher.include</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The caching of <literal>HttpServletRequest</literal>
- objects is now compliant with the servlet
- specification.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateException</literal>s was suppressed
- in certain situations resulting in half-rendered pages
- without error message.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Bugfix: FreeMarker didn't work if the
- <literal>javax.servlet</literal> classes was not available,
- because <literal>Configuration</literal> explicitly referred to
- <literal>javax.servlet.ServletContext</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: classes may were not found if they was available
- only in the <literal>WEB-INF</literal>, and FreeMarker tried to
- load the class dynamically.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: the <literal>Template</literal> constructor (and
- thus <literal>Configuration.getTemplate</literal>) sometimes
- threw <literal>TokenMgrError</literal> (a non-checked exception)
- instead of <literal>ParseException</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The Web application related examples has been
- replaced.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>The history of the releases before the final version</title>
-
- <section>
- <title>Differences between the final and RC2 releases</title>
-
- <itemizedlist>
- <listitem>
- <para>You can load settings from
- <literal>.properties</literal> file with the
- <literal>setSettings</literal> method of
- <literal>Configuration</literal> and other
- <literal>Configurable</literal> subclasses.</para>
- </listitem>
-
- <listitem>
- <para>New string built-in:
- <literal>uncap_first</literal></para>
- </listitem>
-
- <listitem>
- <para>Bugfix: When exposing an XML document to a template and
- accessing it with XPath using Jaxen a
- <literal>ClassCastException</literal> has occurred.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: The template cache has loaded templates with bad
- <literal>Configuration</literal> instance in certain
- situations if you use not the static default
- <literal>Configuration</literal> instance.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the RC2 and RC1 releases</title>
-
- <itemizedlist>
- <listitem>
- <para>Non backward compatible change!:
- <literal>FreemarkerServlet</literal> now uses the encoding of
- the template file for the output, unless you specify the
- encoding in the <literal>ContentType</literal> init-param,
- such as <literal>text/html; charset=UTF-8</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Non backward compatible change compared to RC1!: The
- <literal>capture_output</literal> transform creates variable
- in the current namespace (as <literal>assign</literal>
- directive) with the <literal>var</literal> parameter, not a
- global variable.</para>
- </listitem>
-
- <listitem>
- <para>The new and preferred way of number formatting with
- <literal>string</literal> built-in is
- <literal>foo?string(format)</literal>, instead of the less
- natural <literal>foo?string[format]</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>string</literal> built-in works for boolean
- values. For example: <literal>${spamFilter?string("enabled",
- "disabled")}</literal>. For more information <link
- linkend="ref_builtin_string_for_boolean">read the
- reference</link>.</para>
- </listitem>
-
- <listitem>
- <para>The default strings for outputting boolean value using
- the <literal>string</literal> built-in can be set using the
- <literal>boolean_format</literal> setting.</para>
- </listitem>
-
- <listitem>
- <para>String literals can be quoted with apostrophe-quote.
- <literal>"foo"</literal> and <literal>'foo'</literal> are
- equivalent.</para>
- </listitem>
-
- <listitem>
- <para>The new interface
- <literal>freemarker.cache.CacheStorage</literal> allows users
- to plug custom template caching strategies with the
- <literal>cache_storage</literal> setting. The core package now
- ships with two implementations:
- <literal>SoftCacheStorage</literal> and
- <literal>StrongCacheStorage</literal>. For more information
- read: <xref linkend="pgui_config_templateloading"/></para>
- </listitem>
-
- <listitem>
- <para>You can set settings with string name and string value
- with the new <literal>setSetting(String key, String
- value)</literal> method of <literal>Configurable</literal>
- super-classes (as <literal>Configuration</literal>).</para>
- </listitem>
-
- <listitem>
- <para>Other new <literal>Configuration</literal> methods:
- <literal>getTemplateLoader</literal>,
- <literal>clone</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>assign</literal>, <literal>local</literal> and
- <literal>global</literal> now can capture the output generated
- be the nested template fragment into the variable. This
- deprecates <literal>capture_output</literal> transform. More
- information: <link linkend="ref.directive.assign">assign
- directive reference</link></para>
- </listitem>
-
- <listitem>
- <para>Other new <literal>Configuration</literal> methods:
- <literal>getTemplateLoader</literal>,
- <literal>clone</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>TemplateTransformModel</literal>
- interface: <literal>getWriter</literal> can throw
- <literal>IOException</literal>, and can return
- <literal>null</literal> if the transform does not support body
- content.</para>
- </listitem>
-
- <listitem>
- <para>Till now
- <literal>TemplateTransformModel.getWriter</literal> has
- received null as parameter map if the transform was called
- without parameters. From now, it will receive an empty Map
- instead. Note that the previous API documentation didn't state
- that it always receives null if there are no parameters, so
- hopelessly only very few classes exploit this design
- mistake.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>TemplateControl</literal> interface:
- <literal>onStart</literal> and <literal>afterBody</literal>
- methods are now allowed to throw
- <literal>IOException</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Path that contains <literal>//:</literal> is considered
- as absolute path.</para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_builtins_string">string
- built-ins</link>: <literal>index_of</literal>,
- <literal>last_index_of</literal>,
- <literal>starts_with</literal>, <literal>ends_with</literal>,
- <literal>replace</literal>, <literal>split</literal>,
- <literal>chop_linebreak</literal>.</para>
- </listitem>
-
- <listitem>
- <para>New <link linkend="ref_builtins_sequence">sequence
- built-ins</link>: <literal>sort</literal>,
- <literal>sort_by</literal>.</para>
- </listitem>
-
- <listitem>
- <para>All <literal>Configuration</literal> level settings can
- by set with Servlet init-params
- (<literal>template_exception_handler</literal>,
- <literal>locale</literal>, <literal>number_format</literal>,
- etc.).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: classes may were not found if they was available
- only in the <literal>WEB-INF</literal>, and FreeMarker tried
- to load the class dynamically.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>setLocalizedLookup(false)</literal> of
- <literal>Configuration</literal> was overridden when you have
- called <literal>setTemplateLoader</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: String concatenation had performance problem if
- there was multiple concatenations chained, as:
- <literal>"a"+x+"a"+x+"a"+x+"a"+x+"a"+x</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: white-space stripping was not worked with tags
- spanning over multiple lines.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Removing several dependencies on JDK 1.3, so
- FreeMarker can be build for JDK 1.2.2.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 2 and RC1 releases</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>ftl</literal> is now stricter, and does not
- allow custom parameters. To associate custom attributes to
- templates, we may add a new directive later, if there is a
- demand for it.</para>
- </listitem>
-
- <listitem>
- <para><literal>escape</literal> directive does not affect
- numerical interpolations
- (<literal>#{<replaceable>...</replaceable>}</literal>)
- anymore, as it has caused errors with string escapes as
- <literal>?html</literal>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>normalizeName</literal> method of
- <literal>freemarker.cache.TemplateLoader</literal> has been
- removed, because it has caused too many complications.
- Instead, normalization happens on a single point in the
- <literal>TempateCache</literal>. In consequence, FreeMarker is
- now stricter about the format of template paths, as things
- like <literal>/../</literal> are interpreted by the
- core.</para>
- </listitem>
-
- <listitem>
- <para>Experimental feature: With the
- <literal>setSimpleMapWrapper</literal> method of
- <literal>BeansWrapper</literal> you can configure it to wrap
- <literal>java.util.Map</literal>-s as
- <literal>TemplateHashModelEx</literal>-s, and do not expose
- the methods of the object.</para>
- </listitem>
-
- <listitem>
- <para>New <literal>Configuration</literal> methods:
- <literal>set/getLocalizedLookup</literal>,
- <literal>clearTemplateCache</literal>,
- <literal>clearSharedVariables</literal>.</para>
- </listitem>
-
- <listitem>
- <para>More cleanups in the <literal>Environment</literal>
- API.</para>
- </listitem>
-
- <listitem>
- <para>Better JSP standard compliance: JSP page-scope variables
- are the global variables that were created in the template
- (not the variables of the data-model).</para>
- </listitem>
-
- <listitem>
- <para>JDOM independent XML-wrapping:
- <literal>freemarker.ext.xml.NodeListModel</literal> is a
- re-implementation of
- <literal>freemarker.ext.jdom.NodeListModel</literal> that does
- not rely on JDOM; you don't need JDOM .jar anymore. The new
- <literal>NodeListModel</literal> automatically uses W3C DOM,
- dom4j, or JDOM, depending on which library is available (that
- is, depending on what object do you pass to its
- constructor).</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>WebappTemplateLoader</literal>:
- Template updating didn't worked correctly with Tomcat due the
- caching of resources. Now
- <literal>WebappTemplateLoader</literal> tries to access the
- resources directly as <literal>File</literal>, if it is
- possible, thus bypasses the caching.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Templates loaded with
- <literal>MultiTemplateLoader</literal> subclasses was removed
- from the template cache after the template update delay has
- elapsed (5 seconds by default) even if the template file was
- unchanged. This can cause lot of extra load for a high-traffic
- server if you have many templates or if the template update
- delay was set to 0 second.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Undefined variables in hash and sequence
- constructors (as <literal>[a, b, c]</literal>) didn't caused
- errors.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the Preview 1 and Preview 2
- releases</title>
-
- <itemizedlist>
- <listitem>
- <para>All 16-bit Unicode letters and numbers are allowed in
- identifiers, as well as the <literal>$</literal> character (as
- in Java programming language). Thus you can use accented
- letters, Arabic letters, Chinese letters, etc. as identifiers
- in templates</para>
- </listitem>
-
- <listitem>
- <para>Macros now can create loop variables for the nested
- content. For more information <link
- linkend="dgui_misc_userdefdir_loopvar">read
- this</link>.</para>
- </listitem>
-
- <listitem>
- <para>New directives: <literal>t</literal>,
- <literal>rt</literal> and <literal>lt</literal> directives
- allow you to do explicit white-space removal in extreme FTL
- applications. For more information read <link
- linkend="ref.directive.t">the reference</link>.</para>
- </listitem>
-
- <listitem>
- <para>The syntax of assignment-with-namespace has changed from
- <literal><#assign foo=123 namespace=myLib></literal>) to
- <literal><#assign foo=123 in myLib></literal>, since the
- previous syntax was confusing because its similarity to a
- bulk-assignment.</para>
- </listitem>
-
- <listitem>
- <para>Bulk assignments (as <literal><#assign x=1, y=2,
- z=3></literal>) no longer need colon to separate the
- assignments (as <literal><#assign x=1 y=2
- z=3></literal>), although it is still allowed to preserve
- backward compatibility.</para>
- </listitem>
-
- <listitem>
- <para>Positional parameter passing is supported for macro
- calls as shorthand form of normal named parameter passing. For
- more details read <link
- linkend="ref_directive_userDefined_positionalParam">read the
- reference</link>.</para>
- </listitem>
-
- <listitem>
- <para>New built-in, <literal>namespace</literal>, to get the
- namespace of the currently executing macro.</para>
- </listitem>
-
- <listitem>
- <para><literal>TransformControl</literal> interface (was
- experimental earlier): If the <literal>Writer</literal>
- returned by
- <literal>TemplateTransformModel.getWriter</literal> implements
- this interface, it can instruct the engine to skip or to
- repeat evaluation of the nested content, and gets notified
- about exceptions that are thrown during the nested content
- evaluation. For more information please read the API
- documentation.</para>
- </listitem>
-
- <listitem>
- <para>Jython wrapper can now wrap arbitrary Java objects, not
- only <literal>PyObject</literal>-s. If an object is passed to
- the wrapper that is neither a
- <literal>TemplateModel</literal>, nor a
- <literal>PyObject</literal>, it is first coerced into a
- <literal>PyObject</literal> using Jython's own wrapping
- machinery, and then wrapped into a
- <literal>TemplateModel</literal> as any other
- <literal>PyObject</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Some cleanups in the <literal>Environment</literal>
- API.</para>
- </listitem>
-
- <listitem>
- <para>The Web application related examples has been
- replaced.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Templates loaded with
- <literal>URLTemplateLoader</literal> subclasses was removed
- from the template cache after the template update delay has
- elapsed (5 seconds by default) even if the template file was
- unchanged. This can cause lot of extra load for a high-traffic
- server if you have many templates or if the template update
- delay was set to 0 second.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>FreeMarkerServlet</literal> has thrown
- <literal>ServletException</literal> even if a debug
- <literal>TemplateException</literal> handler was in use (so
- you may got Error 500 page instead of debug
- information).</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
- </section>
-
- <section xml:id="versions_2_1_5">
- <title>2.1.5</title>
-
- <para>Date of release: 2003-02-08</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: Fixed a bug that forced the cache to frequently
- reload templates accessed through URL and multi template
- loaders: Templates loaded with
- <literal>URLTemplateLoader</literal> subclasses and
- <literal>MultiTemplateLoader</literal> was removed from the
- template cache after the template update delay has elapsed (5
- seconds by default) even if the template file was unchanged.
- This can cause lot of extra load for a high-traffic server if
- you have many templates or if the template update delay was set
- to 0 second.)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Many anomalies in the
- <literal>JythonWrapper</literal> were resolved, making the
- integration with Jython much smoother: Jython wrapper can now
- wrap arbitrary Java objects, not only
- <literal>PyObject</literal>-s. If an object is passed to the
- wrapper that is neither a <literal>TemplateModel</literal>, nor
- a <literal>PyObject</literal>, it is first coerced into a
- <literal>PyObject</literal> using Jython's own wrapping
- machinery, and then wrapped into a
- <literal>TemplateModel</literal> as any other
- <literal>PyObject</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_1_4">
- <title>2.1.4</title>
-
- <para>Date of release: 2002-12-26</para>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: Log4J is now found when automatically discovering
- the logging library to use.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: An exception is no longer thrown in the static
- initializer of the <literal>Configuration</literal> if the
- directory specified in the <literal>"user.dir"</literal> system
- property is not readable.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_1_3">
- <title>2.1.3</title>
-
- <para>Date of release: 2002-12-09</para>
-
- <section>
- <title>Changes on the FTL side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: <literal>cap_first</literal> built-in did what
- <literal>double</literal> built-in does.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The official extension of FreeMarker template files is
- <literal>ftl</literal> from now, not <literal>fm</literal>.
- (This is the name of the template language; FTL, for FreeMarker
- Template Language.) Of course you can use any extensions, since
- FreeMarker does not deal with the file extension. But we
- recommend <literal>ftl</literal> extension as default.</para>
- </listitem>
-
- <listitem>
- <para>Web application examples got tweaked again, as under JDK
- 1.4 a class in an explicit (named) package can no longer import
- classes from the default (unnamed) package. Our webapp example
- was using classes in the default package, they are now moved
- into named packages.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_1_2">
- <title>2.1.2</title>
-
- <para>Date of release: 2002-11-28</para>
-
- <section>
- <title>Changes in FTL (FreeMarker Template Language)</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>FreeMarkerServlet</literal> now has a setting for
- the <literal>Content-Type</literal> header of the response,
- defaulting to <literal>text/html</literal>. Previously it set no
- content type, which made it not play nicely when integrated with
- software that expected it (i.e. OpenSymphony SiteMesh).</para>
- </listitem>
-
- <listitem>
- <para><literal>FreeMarkerServlet</literal> now works correctly
- when mapped to an URL extension instead of URL path
- prefix.</para>
- </listitem>
-
- <listitem>
- <para>You can emulate <literal>include</literal> directive call
- within Java code by calling
- <literal>Environment.include(<replaceable>templateName</replaceable>,
- <replaceable>charset</replaceable>,
- <replaceable>parse</replaceable>)</literal>.</para>
- </listitem>
- </itemizedlist>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: When <literal>Template.process()</literal> was
- called from another template processing, it set
- <literal>currentEnvironment</literal> to null when it returned,
- thus crashed the parent template processing.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: the <literal>_descendant</literal> key in JDOM
- support incorrectly left the document root element out of the
- result when applied to a Document node.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: because we incorrectly assumed certain behavior of
- JDK 1.4 Beans introspector, calls to public interface methods on
- non-public classes that implement the interface were causing
- exceptions on JDK 1.4</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Various minor supplements to the manual.</para>
- </listitem>
-
- <listitem>
- <para>Documentation HTML pages don't try to load the SourceForge
- logo from the Internet anymore.</para>
- </listitem>
-
- <listitem>
- <para>The default ant target is <literal>jar</literal>, not
- <literal>dist</literal>.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_1_1">
- <title>2.1.1</title>
-
- <para>Date of release: 2002-11-04</para>
-
- <section>
- <title>Changes in FTL (FreeMarker Template Language)</title>
-
- <itemizedlist>
- <listitem>
- <para>Multi-type variables that are both string and number or
- string and date are now output using their number or date value
- instead of the string value when used in the
- <literal>${...}</literal> interpolation. This practically makes
- the string part of a string/number or a string/date variables
- useless.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: operator ``or'' (<literal>||</literal>) worked
- wrongly when its left operand was a composite expression (e.g.
- the second <literal>||</literal> in <literal>false || true ||
- false</literal>; this was evaluated to <literal>false</literal>,
- but it should be <literal>true</literal>)</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Less-than sign inside comments confused the FTL
- parser (e.g. <literal><#-- blah < blah --></literal>);
- it commented out everything after the problematic
- comment.</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: Comparing two numerical constants (e.g. <literal>3
- == 3</literal>) caused internal error in the FTL parser, and
- aborted template processing with error.</para>
- </listitem>
-
- <listitem>
- <para>Experimental date/time type support was removed, since it
- seems that this initial implementation was misguided. FreeMarker
- 2.2 will certainly support data/time.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para>Bugfix: <literal>Number</literal>s wrapped with
- <literal>BEANS_WRAPPER</literal> was displayed with the
- <literal>toString()</literal> method of wrapped object. Now they
- are rendered according to the <literal>number_format</literal>
- setting, because multi-type variables that are both string and
- number are now output using their number value instead of the
- string value.</para>
- </listitem>
-
- <listitem>
- <para>Experimental date/time type support was removed, since it
- seems that this initial implementation was misguided. FreeMarker
- 2.2 will certainly support data/time.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_1">
- <title>2.1</title>
-
- <para>Date of release: 2002-10-17</para>
-
- <para>Templates and the Java API are <emphasis>not</emphasis> fully
- compatible with 2.0 releases. You will need to revisit existing code
- and templates, or use 2.1 for new projects only. Sorry for this
- inconvenience; FreeMarker has undergone some revolutionary changes
- since the 1.x series. We hope things will soon be sufficiently mature
- for us to offer (almost) backward-compatible releases. Note that there
- is a backward-compatibility flag that can be set via
- <literal>Configuration.setClassicCompatible(true)</literal> that
- causes the new FreeMarker to emulate most of FreeMarker 1.x's
- quirks.</para>
-
- <section>
- <title>Changes in FTL (FreeMarker Template Language)</title>
-
- <itemizedlist>
- <listitem>
- <para>More strict, reveals accidental mistakes in the templates,
- prevents showing incorrect information when something went
- wrong:</para>
-
- <itemizedlist>
- <listitem>
- <para>An attempt to access an undefined variable causes an
- error and aborts template processing (by default at least;
- see later). In earlier versions undefined variables were
- silently treated as empty (zero-length) strings. However,
- you can handle undefined variables in the template with some
- new built-ins. For example,
- <literal>${foo?if_exists}</literal> is equivalent with the
- <literal>${foo}</literal> of earlier versions. Another way
- of looking at this is that null values no longer exist from
- the viewpoint of a template designer. Anything referenced
- must be a defined variable.</para>
-
- <para>Note however that the programmer can configure
- FreeMarker so that it ignores certain errors (say, undefined
- variables), and continues template processing by skipping
- the problematic part. This ``loose'' policy should be used
- only for sites that don't show critical information.</para>
- </listitem>
-
- <listitem>
- <para>New variable type: <link
- linkend="gloss.boolean">boolean</link>. Conditions in
- <literal>if</literal>/<literal>elseif</literal> and operands
- of logical operators (<literal>&&</literal>,
- <literal>||</literal>, <literal>!</literal>) must be
- booleans. Empty strings are no longer treated as a logical
- false.</para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>Local and global variables. More info: <xref
- linkend="dgui_misc_var"/></para>
-
- <itemizedlist>
- <listitem>
- <para>Local variables for macros. You can create/replace
- local variables in macro definition bodies with the <link
- linkend="ref.directive.local"><literal>local</literal>
- directive</link></para>
- </listitem>
-
- <listitem>
- <para>You can create/replace global (non-local) variables
- with the <link
- linkend="ref.directive.global"><literal>global</literal>
- directive</link></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="ref.directive.include"><literal>include</literal></link>
- directive now by default treats the passed filename as being
- relative to the including template's path. To specify absolute
- template paths, you now have to prepend them with a
- slash.</para>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="ref.directive.include"><literal>include</literal></link>
- directive can now use the <emphasis>acquisition
- algorithm</emphasis> (familiar from the Zope system) to look up
- the template to include. Basically, if a template is not found
- where it is looked up first, it is looked up in parent
- directories. This is however not a default behavior, rather it
- is triggered by a new syntactic element.</para>
- </listitem>
-
- <listitem>
- <para>Strict syntax mode: Allows you to generate arbitrary SGML
- (XML) without worrying about clashes with FreeMarker directives.
- For more information read: <xref
- linkend="ref_depr_oldsyntax"/></para>
- </listitem>
-
- <listitem>
- <para>Terse comments: you can use <literal><#--
- <replaceable>...</replaceable> --></literal> instead of
- <literal><comment><replaceable>...</replaceable></comment></literal></para>
- </listitem>
-
- <listitem>
- <para>Directive that you can use to change the locale (and other
- settings) inside the template: <link
- linkend="ref.directive.setting"><literal>setting</literal></link></para>
- </listitem>
-
- <listitem>
- <para>Directive to explicitly flush the output buffer: <link
- linkend="ref.directive.flush"><literal>flush</literal></link></para>
- </listitem>
-
- <listitem>
- <para>The top-level (root) hash is available via the variable
- <literal>root</literal>, which is now a reserved name.</para>
- </listitem>
-
- <listitem>
- <para>The misnamed <literal>function</literal> directive has
- been renamed to <literal>macro</literal>.</para>
- </listitem>
-
- <listitem>
- <para>String literals support various new <link
- linkend="topic.escapeSequence">escape sequences</link>,
- including UNICODE escapes
- (<literal>\x<replaceable>CODE</replaceable></literal>)</para>
- </listitem>
-
- <listitem>
- <para>The <link
- linkend="ref.directive.compress"><literal>compress</literal></link>
- directive is now more conservative in removing line
- breaks.</para>
- </listitem>
-
- <listitem>
- <para>Built-in to capitalize the first word: <link
- linkend="ref_builtin_cap_first"><literal>cap_first</literal></link></para>
- </listitem>
-
- <listitem>
- <para>Built-in to generate on-the-fly templates: <link
- linkend="ref_builtin_interpret"><literal>interpret</literal></link></para>
- </listitem>
-
- <listitem>
- <para><link
- linkend="ref.directive.stop"><literal>stop</literal></link>
- directive has an optional parameter to describe the reason of
- termination</para>
- </listitem>
-
- <listitem>
- <para>Better error messages.</para>
- </listitem>
-
- <listitem>
- <para>New variable type: date. <emphasis>Date support is
- experimental. It can change substantially in the future. Keep
- this in mind if you use it.</emphasis></para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes on the Java side</title>
-
- <itemizedlist>
- <listitem>
- <para><literal>ObjectWrapper</literal>: You can put
- non-<literal>TemplateModel</literal> objects directly into
- hashes, sequences and collections, and they will be
- automatically wrapped with the appropriate
- <literal>TemplateModel</literal> implementation. The API of
- objects that are exposed to templates
- (<literal>Simple<replaceable>XXX</replaceable></literal>) has
- been changed according to this, for example in
- <literal>SimpleHash</literal> the old <literal>put(String key,
- TemplateModel value)</literal> is now <literal>put(String key,
- Object object)</literal>. Also, you can pass any kind of object
- as data-model to <literal>Template.process</literal>. The
- alternative reflection based <literal>ObjectWrapper</literal>
- can expose the members of any Java object automatically for the
- designer. More information: <link
- linkend="pgui_datamodel_objectWrapper">Object wrapping</link>,
- <link linkend="pgui_misc_beanwrapper">Bean wrapper</link>, <link
- linkend="pgui_misc_jythonwrapper">Jython wrapper</link>.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>Configuration</literal> object was introduced
- as a central point to hold all your FreeMarker-related global
- settings, as well as commonly used variables that you want to
- have available from any template. Also it encapsulates the
- template cache and can be used to load templates. For more
- information read <xref linkend="pgui_config"/>.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateLoader</literal>: pluggable template
- loader, separates caching from template loading</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateNumberModel</literal>-s do not control
- their formatting anymore. They just store the data (i.e. a
- number). Number formatting is done by the FreeMarker core based
- on the <literal>locale</literal> and
- <literal>number_format</literal> settings. This logic applies to
- the new experimental date type as well.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateBooleanModel</literal> introduced: Only
- objects that implements this interface can be used as a boolean
- in true/false conditions. More info: <xref
- linkend="pgui_datamodel_scalar"/></para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateDateModel</literal> introduced: objects
- that implements this interface are recognized as dates and can
- be locale-sensitively formatted. <emphasis>Date support is
- experimental in FreeMarker 2.1. It can change substantially in
- the future. Keep this in mind if you use it.</emphasis></para>
- </listitem>
-
- <listitem>
- <para>The <literal>TemplateModelRoot</literal> interface was
- deprecated. As of FreeMarker 2.1, you can simply use any
- instance of <literal>TemplateHashModel</literal> instead. This
- actually is due to a significant architectural change. Variables
- set or defined in a template are stored in a separate
- <literal>Environment</literal> object that only exists while the
- template is being rendered. Thus, the template doesn't modify
- the root hash.</para>
- </listitem>
-
- <listitem>
- <para>Changes to transformations</para>
-
- <itemizedlist>
- <listitem>
- <para>Completely rewritten
- <literal>TemplateTransformModel</literal> interface. More
- flexible, and does not impose output holding. More
- information: <xref
- linkend="pgui_datamodel_directive"/></para>
- </listitem>
-
- <listitem>
- <para>The <literal>transform</literal> directive now takes
- an optional set of key/value pairs. <literal><transform
- myTransform;
- <replaceable>key1</replaceable>=<replaceable>value1</replaceable>,
- <replaceable>key2</replaceable>=<replaceable>value2</replaceable>
- <replaceable>...</replaceable>></literal>. More
- information: <link
- linkend="ref.directive.transform"><literal>transform</literal>
- directive</link></para>
- </listitem>
-
- <listitem>
- <para>The transforms that ship with the FreeMarker core are
- now available by default to all templates - i.e.
- <literal><transform html_escape></literal> will invoke
- the
- <literal>freemarker.template.utility.HtmlEscape</literal>
- transform. More information: <xref
- linkend="pgui_config_sharedvariables"/></para>
- </listitem>
- </itemizedlist>
- </listitem>
-
- <listitem>
- <para>User-defined <literal>TemplateModel</literal> objects now
- can access the runtime environment (read and set variables, get
- the current locale, etc.) using an
- <literal>Environment</literal> instance, which can be obtained
- by the static
- <literal>Environment.getCurrentEnvironment()</literal> method.
- As a result, <literal>TemplateScalarModel.getAsString</literal>
- has been changed: it has no locale parameter.</para>
- </listitem>
-
- <listitem>
- <para><literal>TemplateExceptionHandler</literal>-s make it
- possible to define your own rules on what to do when a runtime
- error occurs (e.g. accessing a non existing variable) during
- template processing. For example, you can abort template
- processing (recommended for most sites), or skip the problematic
- statement and continue template processing (similar to old
- behavior). DebugMode has been removed, use
- <literal>TemplateExceptionHandler.DEBUG_HANDLER</literal> or
- <literal>HTML_DEBUG_HANDLER</literal> instead.</para>
- </listitem>
-
- <listitem>
- <para>Logging: FreeMarker logs certain events (runtime errors
- for example). For more information read <xref
- linkend="pgui_misc_logging"/>.</para>
- </listitem>
-
- <listitem>
- <para><literal>SimpleIterator</literal> was removed, but we
- provide a <literal>TemplateCollectionModel</literal>
- implementation: <literal>SimpleCollection</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Arithmetic engine is pluggable
- (<literal>Configuration.setArithmeticEngine</literal>). The core
- distribution comes with two engines:
- <literal>ArithmeticEngine.BIGDECIMAL_ENGINE</literal> (the
- default) that converts all numbers to
- <literal>BigDecimal</literal> and then operates on them, and
- <literal>ArithmeticEngine.CONSERVATIVE_ENGINE</literal> that
- uses (more-or-less) the widening conversions of Java language,
- instead of converting everything to
- <literal>BigDecimal</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>freemarker.ext.beans</literal>
- package: The JavaBeans adapter layer has suffered several major
- changes. First, <literal>BeansWrapper</literal> is no longer a
- static utility class - you can now create instances of it, and
- every instance can have its own instance caching policy and
- security settings. These security settings are also new - you
- can now create JavaBeans wrappers that hide methods that are
- considered unsafe or inappropriate in a templating environment.
- By default, you can no longer call methods like
- <literal>System.exit()</literal> from the template (although you
- can manually turn off these safeguards). The
- <literal>StaticModel</literal> and
- <literal>StaticModels</literal> classes are gone; their
- functionality is now replaced with the
- <literal>BeansWrapper.getStaticModels()</literal> method.</para>
- </listitem>
-
- <listitem>
- <para><literal>freemarker.ext.jython</literal> package:
- FreeMarker can now directly use Jython objects as data-models
- using the <link linkend="pgui_misc_jythonwrapper">Jython
- wrapper</link>.</para>
- </listitem>
-
- <listitem>
- <para>Changes to <literal>freemarker.ext.jdom</literal> package:
- The package now uses the <emphasis>Jaxen</emphasis> package
- instead of its predecessor, the
- <emphasis>werken.xpath</emphasis> package to evaluate XPath
- expressions. Since <emphasis>Jaxen</emphasis> is a successor to
- <emphasis>werken.xpath</emphasis>, this can be considered to be
- an upgrade. As a consequence, namespace prefixes are now
- recognized in XPath expressions and the overall XPath
- conformance is better.</para>
- </listitem>
-
- <listitem>
- <para>Better error reporting: If the processing of a template is
- aborted by a <literal>TemplateException</literal> being thrown,
- or using a <literal><#stop></literal> directive,
- FreeMarker will now output an execution trace with line and
- column numbers relative to the template source.</para>
- </listitem>
-
- <listitem>
- <para>The output is written to a simple
- <literal>Writer</literal>; no more
- <literal>PrintWriter</literal>. This redesign causes FreeMarker
- to no longer swallow <literal>IOException</literal>s during
- template processing.</para>
- </listitem>
-
- <listitem>
- <para>Various API cleanups, primarily the removing of
- superfluous constructor and method overloads.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Other changes</title>
-
- <itemizedlist>
- <listitem>
- <para>Documentation has been rewritten from scratch</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Differences between the RC1 and final release</title>
-
- <itemizedlist>
- <listitem>
- <para>Added the support for date models and locale-sensitive
- date formatting. <emphasis>Date support is experimental in
- FreeMarker 2.1. It can change substantially in the future. Keep
- this in mind if you use it.</emphasis></para>
- </listitem>
-
- <listitem>
- <para>Added the <literal>default</literal> built-in which makes
- it possible to specify default values for undefined
- expressions.</para>
- </listitem>
-
- <listitem>
- <para><literal>SimpleIterator</literal> has been removed,
- <literal>SimpleCollection</literal> has been introduced</para>
- </listitem>
-
- <listitem>
- <para>Arithmetic engine is pluggable. The core now contains two
- arithmetic engines:
- <literal>ArithmeticEngine.BIGDECIMAL_ENGINE</literal> and
- <literal>ArithmeticEngine.CONSERVATIVE_ENGINE</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>BeansWrapper</literal> supports a new exposure
- level: <literal>EXPOSE_NOTHING</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>Constants</literal> interface was removed.
- <literal><replaceable>...</replaceable>_WRAPPER</literal>
- constants have been moved from <literal>Constants</literal> to
- <literal>ObjectWrapper</literal>,
- <literal>EMPTY_STRING</literal> constant was moved to
- <literal>TemplateScalarModel</literal>,
- <literal>NOTHING</literal> constant was moved to
- <literal>TemplateModel</literal>, <literal>TRUE</literal> and
- <literal>FALSE</literal> constants were moved to
- <literal>TemplateBooleanModel</literal>.</para>
- </listitem>
-
- <listitem>
- <para><literal>JAVABEANS_WRAPPER</literal> was renamed to
- <literal>BEANS_WRAPPER</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration.get</literal> and
- <literal>put</literal>, <literal>putAll</literal> were renamed
- to <literal>getSharedVariable</literal> and
- <literal>setSharedVariable</literal>,
- <literal>setAllSharedVariables</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>Configuration.getClassicCompatibility</literal>,
- <literal>setClassicCompatibility</literal> were renamed to
- <literal>isClassicCompatible</literal>,
- <literal>setClassicCompatible</literal></para>
- </listitem>
-
- <listitem>
- <para><literal>Template.process</literal> method overloads with
- <literal>useReflection</literal> parameter was removed. But now
- we have <literal>setObjectWrapper</literal> method in the
- <literal>Configuration</literal>, so you can set the preferred
- root-object wrapper there.</para>
- </listitem>
-
- <listitem>
- <para>Some superfluous method overloads were removed; these
- changes are backward compatible with RC1</para>
- </listitem>
-
- <listitem>
- <para>Various minor JavaDoc and Manual improvements</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: <literal>include</literal> directive has
- calculated the base path of relative paths wrongly</para>
- </listitem>
-
- <listitem>
- <para>Bugfix: We have accidentally used a J2SE 1.3 class, but
- FreeMarker 2.1 must able to run on J2SE 1.2</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_01">
- <title>2.01</title>
-
- <para>The main improvement is in error reporting. Now exceptions are
- much more informative since they come with complete line number and
- column information.</para>
-
- <para>The only API change between 2.0 and 2.01 was the elimination of
- the CacheListener/CacheEvent API. Now, if the updating of a template
- file fails, the exception is thrown back to the caller to handle. If
- you want logging to occur when a template file is updated
- successfully, you can override the logFileUpdate() method in
- FileTemplateCache.</para>
- </section>
-
- <section xml:id="versions_2_0">
- <title>2.0</title>
-
- <para>FreeMarker 2.0 final was released on 18 April 2002. The changes
- with respect to the previous release, 2.0 RC3 are fairly minor.</para>
-
- <section>
- <title>Bugfixes</title>
-
- <itemizedlist>
- <listitem>
- <para>There were a couple of bugs in handling null values, where
- Lazarus did not do the same thing as FreeMarker Classic.
- Traditionally, in FreeMarker, nulls were treated as being
- equivalent to an empty string in the appropriate context. At
- this point, to the best of our knowledge, there is backward
- compatibility with FreeMarker Classic in this respect.</para>
- </listitem>
-
- <listitem>
- <para>Literal strings can now include line breaks. This was a
- backward compatibility issue with FreeMarker Classic that has
- been fixed.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes to the Template language</title>
-
- <itemizedlist>
- <listitem>
- <para>You can use the extra built-in of
- <literal>myString?web_safe</literal> to convert a string to its
- "web-safe" equivalent, where problematic characters such as
- '<' are converted to &lt;.</para>
- </listitem>
-
- <listitem>
- <para>In displaying numbers with a fractional part, the
- rendering apparatus now respects the decimal separator of the
- template's locale, so that, for example, in continental Europe,
- you would see 1,1 and in the U.S. locale, 1.1.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes to the API</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>getAsString()</literal> method in the
- <literal>TemplateScalarModel</literal> interface now takes a
- <literal>java.util.Locale</literal> as a parameter. For the most
- part, this is a hook for later use. In the default
- implementation, <literal>SimpleScalar</literal>, this parameter
- is unused. If you are implementing this interface yourself, your
- implementation may ignore the parameter. However, it will be
- appealing for certain implementations.</para>
- </listitem>
-
- <listitem>
- <para>The constructors of <literal>FileTemplateCache</literal>
- have changed. If you are using an absolute directory on the file
- system as the location of your templates, you need to pass in an
- instance of <literal>java.io.File</literal> to indicate the
- location. If you use the constructors that take a string, this
- is taken to mean relative to the classloader classpath.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Miscellany</title>
-
- <para>The ant build script build.xml now contains a target that
- builds a .war file containing the Hello, World and Guestbook
- examples. It builds a fmexamples.war. For example, if you are using
- Tomcat in its out-of-the-box configuration, you would place this
- under <TOMCAT_HOME>/webapps and then you would use
- http://localhost:8080/fmexamples/servlet/hello and
- http://localhost:8080/fmexamples/servlet/guestbook for the Hello,
- World and Guestbook examples respectively.</para>
- </section>
- </section>
-
- <section xml:id="versions_2_0RC3">
- <title>2.0 RC3</title>
-
- <para>FreeMarker 2.0 RC3 was released on 11 April 2002. This release
- was primarily devoted to fixing bugs that were reported in RC2.</para>
-
- <section>
- <title>Bug Fixes</title>
-
- <itemizedlist>
- <listitem>
- <para>Variables defined in an <include...> were not
- available in the enclosing page. This has been fixed.</para>
- </listitem>
-
- <listitem>
- <para>The JavaCC parser was not configured to handle Unicode
- input correctly. Now, Unicode support is working.</para>
- </listitem>
-
- <listitem>
- <para>There was a bug when comparing a number with null. It
- should have returned false, but threw an exception instead. This
- has been fixed.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Changes to the Template Language</title>
-
- <itemizedlist>
- <listitem>
- <para>The syntax of the include directive has changed. To
- indicate an unparsed include file, you do as follows:</para>
-
- <programlisting role="template"><include "included.html" ; parsed="n" ></programlisting>
-
- <para>You can also indicate the encoding of the included file
- this way:</para>
-
- <programlisting role="template"> <include "included.html" ; encoding="ISO-8859-5"></programlisting>
- </listitem>
-
- <listitem>
- <para>The built-in myString?trim was added for trimming the
- leading and trailing white-space from strings.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>API changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The TemplateEventAdapter machinery was taken out. This was
- never set up in a very useful manner and we anticipate that
- version 2.1 will have more complete support for logging
- events.</para>
- </listitem>
-
- <listitem>
- <para>The template caching mechanism was streamlined and
- simplified.</para>
- </listitem>
-
- <listitem>
- <para>The FileTemplateCache can now be configured to load files
- relative to a class loader, using the Class.getResource() call.
- This allows templates to be bundled up in .jar files or in a
- .war file for easy deployment of web-based apps.</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_0RC2">
- <title>2.0 RC2</title>
-
- <para>FreeMarker 2.0 RC 2 was released on 4 April 2002. Here is a
- summary of changes wrt to the first release.</para>
-
- <section>
- <title>Changes to Template Language</title>
-
- <itemizedlist>
- <listitem>
- <para>Certain built-in functionality is provided via a new
- operator, '?'. Thus, <literal>myList?size</literal> provides the
- number of elements in a list. Similarly,
- <literal>myString?length</literal> provides the length of a
- string, <literal>myString?upper_case</literal> puts the string
- all in capital letters, and <literal>myHash?keys</literal>
- provides a sequence containing the keys in the hash. See <xref
- linkend="ref_builtins"/> for list of all available
- built-ins.</para>
- </listitem>
-
- <listitem>
- <para>Numerical comparisons can now be made using the "natural"
- operators < and > but there are also "web-safe"
- alternatives, such as <emphasis>\lt</emphasis> and
- <emphasis>\gt</emphasis>, since the use of these characters may
- confuse HTML editors and parsers. Note that these changed
- between rc1 and rc2, they now start with a backslash. A little
- asymmetry is the fact that if you use the natural greater-than
- or greater-than-or-equals operators (i.e. > or >=) the
- expression must be in parentheses. With any other operator, the
- parentheses are optional.</para>
- </listitem>
-
- <listitem>
- <para>Within an iteration loop -- i.e. a
- <literal>foreach</literal> or a <literal>list</literal> block --
- the current count in the loop is available as the special
- variable
- <literal><replaceable>index</replaceable>_count</literal>. where
- <replaceable>index</replaceable> is the name of the variable in
- the iteration. A boolean variable called
- <literal><replaceable>index</replaceable>_has_next</literal> is
- also defined that indicates whether there are any more items in
- the iteration after this one. Note that the index starts at
- zero, so you will often be adding one to it in practice.</para>
- </listitem>
-
- <listitem>
- <para>The <literal><#break></literal> directive can now be
- used to break out of a <literal><#foreach...></literal> or
- a <literal><list...></literal> loop. (Prior to this
- version, it only worked within a switch-case block.) There is a
- new directive called <literal><#stop></literal> that, when
- encountered, simply halts processing of the template. This can
- be useful for debugging purposes.</para>
- </listitem>
-
- <listitem>
- <para>When invoking java methods that have been exposed to the
- page, using the code in freemarker.ext.*, there are built-ins
- that allow you to indicate the numerical type that you wish to
- pass as the value. For instance, if you had two methods, one
- that takes an int and another that takes a long, and you wanted
- to pass in a value, you would have to specify which method.
- <literal>myMethod(1?int)</literal> or
- <literal>myMethod(1?long)</literal>. This is unnecessary if
- there is only one method of the given name.</para>
- </listitem>
-
- <listitem>
- <para>Ranges can be used to get the sublist from a list or the
- substring of a string. For example:
- <literal>myList[0..3]</literal> will return items 0 through 3 of
- the list in question. Or, for example, you could get all the
- elements of the list except for the first and last ones via:
- <literal>myList[1..(myList?size-2)]</literal></para>
- </listitem>
-
- <listitem>
- <para>Or we could get the first 6 characters of a string via
- <literal>myString[0..5]</literal></para>
- </listitem>
-
- <listitem>
- <para>Lists can be concatenated using the '+' operator.
- Previously, this overloading of '+' only applied to
- strings.</para>
- </listitem>
-
- <listitem>
- <para>An attempt to compare a number to a string now throws an
- exception, since it is indicative of a coding error. Note that
- there is a backward compatibility mode that can be set (see
- below) that loosens this up in order to be able to process
- legacy templates.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>API Changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>TemplateSequenceModel</literal> interface now
- has a <literal>size()</literal> method for getting the number of
- elements in the sequence in question.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>TemplateModelIterator</literal> interface now
- has a <literal>hasNext()</literal> method.</para>
- </listitem>
-
- <listitem>
- <para>The default sequence and hash implementations,
- <literal>freemarker.template.SimpleSequence</literal> and
- <literal>freemarker.template.SimpleHash</literal> are now
- unsynchronized. If you need the methods to be synchronized, you
- can get a synchronized wrapper via the
- <literal>synchronizedWrapper()</literal> in either class.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>freemarker.utility.ExtendedList</literal> and
- <literal>freemarker.utility.ExtendedHash</literal> classes were
- removed, since all of the extra keys that it defined are now
- available using the appropriate '?' built-in operation, i.e.
- <literal>myHash?keys</literal> or <literal>myList?size</literal>
- or <literal>myList?last</literal>.</para>
- </listitem>
-
- <listitem>
- <para>There is a method in
- <literal>java.freemarker.Configuration</literal> named
- <literal>setDebugMode()</literal> which allows you to decide
- whether stack traces are simply output to the web client (the
- best situation in development) or thrown back to the caller to
- be handled more gracefully (the best situation in
- production).</para>
- </listitem>
-
- <listitem>
- <para>There is a flag that can be set to turn on a processing
- mode that is more backward-compatible with FreeMarker Classic.
- This is off by default, but you can set it via
- <literal>Template.setClassicCompatibility(true)</literal>. What
- this does is that it allows scalars to be treated as a
- single-item list in a list directive. Also, it allows somewhat
- more looseness about types. In FreeMarker 1.x, <literal><#if
- x=="1"></literal> and <literal><#if x==1></literal>
- were in fact equivalent. This meant that legacy templates might
- tend to be slack about this. If classic compatibility is not
- set, an attempt to compare the string "1" with the number 1 will
- result in an exception being thrown. (Note that it is preferable
- to get your templates working without the backward compatibility
- flag, since it usually will require only minor changes. However,
- for people with a lot of templates and no time to check over
- them, this flag may be of use.)</para>
- </listitem>
- </itemizedlist>
- </section>
- </section>
-
- <section xml:id="versions_2_0RC1">
- <title>2.0 RC1</title>
-
- <para>The first public release of FreeMarker 2.0 was on 18 March 2002.
- Here is a summary of the changes in the Lazarus release, with respect
- to the last stable release of FreeMarker Classic.</para>
-
- <para><emphasis>NOTA BENE</emphasis>:</para>
-
- <para>Despite the changes delineated above, the Lazarus release is
- almost entirely backward-compatible with FreeMarker Classic. We
- believe that <emphasis>most</emphasis> existing code and templates
- that work under FreeMarker Classic will continue working under
- Lazarus, with at most minimal changes. In practice, the most common
- cases where legacy template code is broken will be where assumptions
- were made about numbers and strings being equivalent. Note that in
- FreeMarker 2, 2 + 2 does not result in "22". The String "1" and the
- number 1 are entirely different animals and thus, any code will be
- broken if it relies on the boolean expression ("1"==1) being true.
- There is a "classic compatibility mode" that can be set via:
- <literal>Template.setClassCompatibility()</literal> that can be set so
- that Lazarus emulates some of the quirky behavior of FreeMarker
- Classic. However, any code that relied on the above "features" of
- FreeMarker classic really should be reworked. You are less likely to
- run into the other incompatibilities that are listed above. If you
- come across any other anomalies, please do tell us about them.</para>
-
- <section>
- <title>Support for Numerical operations, both arithmetic and
- boolean, as well as numerical ranges.</title>
-
- <itemizedlist>
- <listitem>
- <para>Scalars can now be either strings or numbers. (In
- FreeMarker Classic all scalars were strings.) The basic
- operations allowed are addition, subtraction, multiplication,
- division, and modulus using the <literal>+</literal>,
- <literal>-</literal>, <literal>*</literal>,
- <literal>/</literal>, and <literal>%</literal> operators
- respectively. Arbitrary-precision arithmetic with integers and
- floating point numbers are provided. Though our goal is
- definitely to follow the principle of least surprise, for
- backward compatibility, the <literal>+</literal> operator still
- is used for string concatenation. If either the left hand side
- or the right hand side of <literal>lhs + rhs</literal> is
- non-numerical, we revert to interpreting this as string
- concatenation. Thus, in FreeMarker 2, 2+2 evaluates to the
- number 4, while any of "2"+2 or 2+"2" or "2"+"2" evaluate to the
- string "22". In FreeMarker Classic, rather embarrassingly, all
- of the above, including 2+2, evaluated to the string "22". An
- attempt to use any other arithmetic operator besides the
- <literal>+</literal> with non-numerical operands will cause an
- exception to be thrown.</para>
- </listitem>
-
- <listitem>
- <para>Output of a numerical expression can be made explicit via
- the alternative <literal>#{....}</literal> syntax. If the
- expression within the curly parentheses does not evaluate to a
- numerical value, an exception is thrown. The older ${....}
- syntax can evaluate to either a number or a string. In general,
- if, for logical reasons, the output <emphasis>must</emphasis> be
- numerical, it is preferable to use the #{...} syntax, since it
- adds an extra sanity check. Note that if, by some miracle, the
- character sequence "#{" occurs in your template, you will have
- to use a workaround to prevent problems. (The <noparse>
- directive is one possibility.)</para>
- </listitem>
-
- <listitem>
- <para>In this release, there is a facility for specifying the
- number of digits to show after the decimal point. The following
- code specifies to show at least 3 digits after the decimal point
- but not more than 6. This is optional. This option is only
- available if you use the #{...} syntax.</para>
-
- <programlisting role="template">#{foo + bar ; m3M6} </programlisting>
-
- <para>(Note that the above is something of a stopgap measure.
- Future releases will move toward supporting fully
- internationalization and localization of number and currency
- formatting.</para>
- </listitem>
-
- <listitem>
- <para>Numerical expressions can be used in boolean expressions
- via the comparison operators: <literal>lt</literal>,
- <literal>gt</literal>, <literal>lte</literal>, and
- <literal>gte</literal>. In the web space, where FreeMarker is
- most used in practice, using the more natural operators such as
- < and > would tend to confuse HTML-oriented editors. An
- attempt to compare non-numerical expressions using these
- operators leads to a <literal>TemplateException</literal> being
- thrown. If, by some coincidence, you have variables named "lt",
- "gt", "lte", or "gte", you will have to change their names,
- since they are now keywords in the language.</para>
- </listitem>
-
- <listitem>
- <para>Numerical ranges are supported.</para>
-
- <programlisting role="template"><#list 1990..2001 as year>
- blah blah in the year ${year} blah
-</#list> </programlisting>
-
- <para>The left hand and right hand sides of the
- <literal>..</literal> operator must be numerical, or an
- exception is thrown. They also need not be literal numbers, but
- can be more complex expressions that evaluate to a numerical
- scalar value. Note that it is also possible to write a range
- that descends in value:</para>
-
- <programlisting role="template"><#list 2001..1990 as year>
- blah blah in the year ${year} blah blah
-</#list> </programlisting>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>API Changes</title>
-
- <itemizedlist>
- <listitem>
- <para>The <literal>TemplateNumberModel</literal> interface and
- the <literal>SimpleNumber</literal> implementation were added to
- support exposing numerical values.</para>
- </listitem>
-
- <listitem>
- <para>The <literal>TemplateListModel</literal> API in FreeMarker
- Classic had some design problems -- particularly in terms of
- supporting thread-safe code. It has been deprecated in favor of
- the following API's: <literal>TemplateCollectionModel</literal>
- and <literal>TemplateSequenceModel</literal>. The
- <literal>SimpleList</literal> class was refactored to implement
- the above interfaces (and paradoxically, does not implement the
- TemplateListModel interface.) Code that uses the deprecated
- <literal>TemplateListModel</literal> should be
- refactored.</para>
- </listitem>
-
- <listitem>
- <para>The Expose Package by Attila Szegedi has been made an
- integral part of the FreeMarker distribution and is now under
- the freemarker.ext.* hierarchy. This package provides advanced
- models for representing arbitrary Java objects as template
- models, for representing XML documents as template models, as
- well as classes to facilitate the integration of FreeMarker with
- servlets and Ant.</para>
- </listitem>
-
- <listitem>
- <para>In FreeMarker Classic, there were some utility classes
- such as <literal>freemarker.template.utility.Addition</literal>
- etcetera that existed as workarounds for the lack of numerical
- operations in FreeMarker. Those have been removed and will
- probably not be missed.</para>
- </listitem>
-
- <listitem>
- <para>In FreeMarker Classic, the <literal>SimpleScalar</literal>
- object was mutable, it had a <literal>setValue</literal> method.
- This was fairly obviously a design mistake. Any code that relied
- on this must be refactored. Note that in this release, both
- <literal>SimpleScalar</literal> and the newly introduced
- <literal>SimpleNumber</literal> are both immutable and
- final.</para>
- </listitem>
- </itemizedlist>
- </section>
-
- <section>
- <title>Syntactical Miscellany</title>
-
- <itemizedlist>
- <listitem>
- <para>The if-elseif-else syntax was introduced. FreeMarker
- classic only had if-else. This construct should probably (in the
- opinion of the author of this document -- Revusky) be used in
- preference to switch-case since the switch-case with
- fall-through is a notoriously error-prone construct for most
- mortal men.</para>
- </listitem>
-
- <listitem>
- <para>You can now do a multiple assignment in one
- <assign...> directive. For example: <literal><assign x
- = 1, y = price*items, message="foo"></literal></para>
- </listitem>
-
- <listitem>
- <para>A scalar will no longer be interpreted as a one-item list
- in a <list...> or <#foreach...> block. If you have
- code that relied on this feature, there is an easy workaround,
- since you can simply define a list literal with exactly one
- item.</para>
-
- <programlisting role="template"> <assign y=[x]>
- <emphasis>and then...</emphasis>
- <list y as item>...</list> </programlisting>
- </listitem>
- </itemizedlist>
- </section>
- </section>
- </appendix>
-
- <appendix xml:id="app_install">
- <title>Installing FreeMarker</title>
-
- <indexterm>
- <primary>install</primary>
- </indexterm>
-
- <para>No real installation is needed. Simply ensure that
- <literal>freemarker.jar</literal> is somewhere where your Java
- application's class-loader will find it. In web application this usually
- means putting <literal>freemarker.jar</literal> into the
- <literal>WEB-INF/lib</literal> directory of your web application. (If
- you want to use FreeMarker with JSP Model-2 style (which also means that
- you can use custom JSP taglibs in the templates), some extra steps
- needed. For more information please see <link
- linkend="pgui_misc_servlet">the chapter about servlets</link>.)</para>
-
- <para>FreeMarker has no required dependencies. But to use certain
- <emphasis>optional</emphasis> FreeMarker features, the related party
- libraries have to be available for the class-loader:</para>
-
- <itemizedlist>
- <listitem>
- <para>Jaxen (recommended, <link
- xlink:href="http://jaxen.org/">download here</link>) or Apache Xalan
- is needed for XML XPath support. Please use at least Jaxen
- 1.1-beta-8, not older versions! Apache Xalan classes are included in
- a package-relocated form in Sun/Oracle J2SE 1.4-1.8 (maybe later
- too), and FreeMarker will use those if it doesn't find Jaxen or
- Xalan elsewhere. But as Oracle can change these internal packages
- anytime, it's still recommended to use an external Jaxen or
- Xalan.</para>
- </listitem>
-
- <listitem>
- <para>Obviously, <literal>javax.servlet</literal> classes are needed
- for <literal>FreemarkerServlet</literal>. Servlet version 2.2 or
- later is needed.</para>
- </listitem>
-
- <listitem>
- <para>For the custom JSP taglib support, you will need JSP 1.2 API
- or later avilable. No JSP implementation is needed, just the API.
- This is already present in pretty much every servlet container. For
- more information please see <link linkend="pgui_misc_servlet">the
- chapter about servlets</link>.</para>
- </listitem>
-
- <listitem>
- <para>Jython classes are needed for the Jython wrapper.</para>
- </listitem>
-
- <listitem>
- <para>Pre-1.0 JDOM is needed for the long deprecated
- <literal>freemarker.ext.jdom</literal> package. (When it was
- created, there was no JDOM 1.0 yet.)</para>
- </listitem>
- </itemizedlist>
- </appendix>
-
- <appendix xml:id="app_legal">
- <title>Legal</title>
-
- <section xml:id="app_license">
- <title>License</title>
-
- <indexterm>
- <primary>license</primary>
- </indexterm>
-
- <programlisting role="unspecified">Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
-
-Licensed 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.
-
-==============================================================================
-END LICENSE
-
-
-FreeMarker subcomponents with different copyright owners
---------------------------------------------------------
-
-FreeMarker, both in its source code and binary form (freemarker.jar)
-includes a number of files that are licensed by the Apache Software
-Foundation under the Apache License, Version 2.0. This is the same
-license as the license of FreeMaker, but the copyright owner is the
-Apache Software Foundation. These files are:
-
- freemarker/ext/jsp/web-app_2_2.dtd
- freemarker/ext/jsp/web-app_2_3.dtd
- freemarker/ext/jsp/web-jsptaglibrary_1_1.dtd
- freemarker/ext/jsp/web-jsptaglibrary_1_2.dtd
-
-
-Historical notes
-----------------
-
-FreeMarker 1.x was released under the LGPL license. Later, by
-community consensus, we have switched over to a BSD-style license. As
-of FreeMarker 2.2pre1, the original author, Benjamin Geer, has
-relinquished the copyright in behalf of Visigoth Software Society.
-
-With FreeMarker 2.3.21 the license has changed to Apache License,
-Version 2.0, and the owner has changed from Visigoth Software Society
-to three of the FreeMarker 2.x developers, Attila Szegedi, Daniel
-Dekany, and Jonathan Revusky.</programlisting>
- </section>
-
- <section xml:id="app_eccn">
- <title>Export Control</title>
-
- <para>The FreeMarker source code doesn't include cryptography.
- Furthermore its binary (downloadable) forms don't include any
- cryptography software. Hence, FreeMarker has no Export Control
- Classification Number (ECCN). Where an ECCN should be filled, the
- label "not subject to EAR" could be used.</para>
-
- <para>FreeMarker itself doesn't add any exporting limitations.</para>
- </section>
- </appendix>
- </part>
-
- <glossary xml:id="gloss">
- <glossentry xml:id="gloss.startTag">
- <glossterm>Start-tag</glossterm>
-
- <glossdef>
- <para><link linkend="gloss.tag">Tag</link>, which indicates that the
- following content is under the element, up to the <link
- linkend="gloss.endTag">end-tag</link>. The start-tag may also
- specifies <link linkend="gloss.attribute">attributes</link> for the
- element. An example of a start-tag: <literal><body
- bgcolor=black></literal></para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.boolean">
- <glossterm>Boolean</glossterm>
-
- <glossdef>
- <para>This is a variable type. A boolean variable represents a logical
- true or false (yes or no). For example, if the visitor has been logged
- in or not. There are only two possible boolean values:
- <literal>true</literal> and <literal>false</literal>. Typically, you
- will use booleans with an <literal><#if
- <replaceable>...</replaceable>></literal> directive when you want
- to display text based on some condition, say, you show a certain part
- of the page only for visitors who has logged in.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.character">
- <glossterm>Character</glossterm>
-
- <glossdef>
- <para>A symbol that people use in writing. Examples of characters:
- Latin capital letter A (``A''), Latin small letter A (``a''), digit
- four (``4''), number sign (``#''), colon (``:'')</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.charset">
- <glossterm>Charset</glossterm>
-
- <glossdef>
- <para>A charset is a rule (algorithm) for transforming a sequence of
- <link linkend="gloss.character">characters</link> (text) to a sequence
- of bits (or in practice, to a sequence of bytes). Whenever a character
- sequence is stored on a digital media, or sent through a digital
- channel (network), a charset must be applied. Examples of charsets are
- ISO-8859-1, ISO-8859-6, Shift_JIS , UTF-8.</para>
-
- <para>The capabilities of different charsers are different, that is,
- not all charsets can be used for all languages. For example ISO-8859-1
- can't represent Arabic letters, but ISO-8859-6 can, however it can't
- represent the accented letters that that ISO-8859-1 can. Most charsets
- are highly restrictive regarding the allowed characters. UTF-8 allows
- virtually all possible characters, but most text editors can't handle
- it yet (2004).</para>
-
- <para>When different software components exchange text (as the HTTP
- server and the browser, or the text editor you use for saving
- templates and FreeMarker who loads them), it's very important that
- they agree in the charset used for the binary encoding of the text. If
- they don't, then the binary data will be misinterpreted by the
- receiver (loader) component, which usually results in the distortion
- of the non-English letters.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.outputEncoding">
- <glossterm>Output encoding</glossterm>
-
- <glossdef>
- <para>Means output <link linkend="gloss.charset">charset</link>. In
- the Java world the term ``encoding'' is commonly (mis)used as a
- synonym to ``charset''.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.templateEncoding">
- <glossterm>Template encoding</glossterm>
-
- <glossdef>
- <para>Means template <link linkend="gloss.charset">charset</link>. In
- the Java world the term ``encoding'' is commonly (mis)used as a
- synonym to ``charset''.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.collectionVariable">
- <glossterm>Collection</glossterm>
-
- <glossdef>
- <para>A variable that (in conjunction with the <literal>list</literal>
- directive) can spit out a series of variables.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.dataModel">
- <glossterm>Data-model</glossterm>
-
- <glossdef>
- <para>Something that holds the information the template has to show
- (or use in some other ways) when the template processor assembles the
- output (e.g. a Web page). In FreeMarker this is best visualized as a
- tree.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.directive">
- <glossterm>Directive</glossterm>
-
- <glossdef>
- <para>Instructions to FreeMarker used in <link
- linkend="gloss.FTL">FTL</link> <link
- linkend="gloss.template">templates</link>. They are invoked by <link
- linkend="gloss.FTLTag">FTL tags</link>.</para>
-
- <glossseealso otherterm="gloss.predefinedDirective"/>
-
- <glossseealso otherterm="gloss.userDefinedDirective"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.element">
- <glossterm>Element</glossterm>
-
- <glossdef>
- <para>Elements are the most fundamental building pieces of <link
- linkend="gloss.SGML">SGML</link> documents; an SGML document is
- basically a tree of elements. Example of elements used in HTML: body,
- head, title, p, h1, h2.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.endTag">
- <glossterm>End-tag</glossterm>
-
- <glossdef>
- <para><link linkend="gloss.tag">Tag</link>, which indicates that the
- following content is not under the element. Example:
- <literal></body></literal>.</para>
-
- <glossseealso otherterm="gloss.startTag"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.environment">
- <glossterm>Environment</glossterm>
-
- <glossdef>
- <para>An <literal>Environment</literal> object stores the runtime
- state of a single template <link
- linkend="gloss.templateProcessingJob">template processing job</link>.
- That is, for each
- <literal>Template.process(<replaceable>...</replaceable>)</literal>
- call, an <literal>Environment</literal> instance will be created, and
- then discarded when <literal>process</literal> returns. This object
- stores the set of temporary variables created by the template, the
- value of settings set by the template, the reference to the data-model
- root, etc. Everything that is needed to fulfill the template
- processing job.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.XML">
- <glossterm>Extensible Markup Language</glossterm>
-
- <glossdef>
- <para>A subset (restricted version) of <link
- linkend="gloss.SGML">SGML</link>. This is less powerful than SGML, but
- it easier to learn and much easier to process with programs. If you
- are an HTML author: XML documents are similar to HTML documents, but
- the XML standard doesn't specify the usable elements. XML is a much
- more general-purpose thing than HTML. For example you can use XML to
- describe Web pages (like HTML) or to describe non-visual information
- like a phone book database.</para>
-
- <glossseealso otherterm="gloss.SGML">SGML</glossseealso>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.FTL">
- <glossterm>FreeMarker Template Language</glossterm>
-
- <glossdef>
- <para>Simple programming language designed to write text file
- templates, especially HTML templates.</para>
- </glossdef>
- </glossentry>
-
- <glossentry>
- <glossterm>FTL</glossterm>
-
- <glosssee otherterm="gloss.FTL"/>
- </glossentry>
-
- <glossentry xml:id="gloss.FTLTag">
- <glossterm>FTL tag</glossterm>
-
- <glossdef>
- <para><link linkend="gloss.tag">Tag</link>-like text fragment used to
- invoke FreeMarker <link linkend="gloss.directive">directives</link> in
- <link linkend="gloss.FTL">FTL</link> <link
- linkend="gloss.template">templates</link>. These are similar to HTML
- or XML tags at the first glance. The most prominent difference is that
- the tag name is started with <literal>#</literal> or
- <literal>@</literal>. Another important difference is that FTL tags do
- not use <link linkend="gloss.attribute">attributes</link>, but a
- substantially different syntax to specify parameters. Examples of FTL
- tags: <literal><#if newUser></literal>,
- <literal></#if></literal>, <literal><@menuitem
- title="Projects" link="projects.html"/></literal></para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.hashVariable">
- <glossterm>Hash</glossterm>
-
- <glossdef>
- <para>A variable that acts as a container that stores sub variables
- that can be retrieved via a string that is a lookup name.</para>
-
- <glossseealso otherterm="gloss.sequenceVariable"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.lineBreak">
- <glossterm>Line break</glossterm>
-
- <glossdef>
- <para>Line break is a special character (or a sequence of special
- characters) that causes a line breaking when you see the text as plain
- text (say, when you read the text with Windows notepad). Typically you
- type this character by hitting ENTER or RETURN key. The line break is
- represented with different characters on different platforms (to cause
- incompatibility and confusion...): ``line feed'' character on UNIX-es,
- ``carriage return'' character on Macintosh, ``carriage return''+``line
- feed'' (two characters!) on Windows and DOS. Note that line breaks in
- HTML do not have a visual effect when viewed in a browser; you must
- use markup such as <literal><BR></literal> for that. This manual
- never means <literal><BR></literal> when it says
- ``line-break''.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.macroDefinitionBody">
- <glossterm>Macro definition body</glossterm>
-
- <glossdef>
- <para>The template fragment between the <literal><#macro
- <replaceable>...</replaceable>></literal> and
- <literal></#macro></literal>. This template fragment will be
- executed when you call the macro (for example as
- <literal><@myMacro/></literal>).</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.methodVariable">
- <glossterm>Method</glossterm>
-
- <glossdef>
- <para>A variable that calculates something based on parameters you
- give, and returns the result.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.MVC">
- <glossterm>MVC pattern</glossterm>
-
- <glossdef>
- <para>MVC stands for Model View Controller. It's a design pattern
- started his life in the 70's as a framework developer by Trygve
- Reenskaug for Smalltalk, and was used primary for for UI-s (user
- interfaces). MVC considers three roles:</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- <para>Model: Model represents application (domain) specific
- information in a non-visual way. For example, an array of product
- objects in the memory of your computer is the part of the
- model.</para>
- </listitem>
-
- <listitem>
- <para>View: View displays the model and provides UI. For example,
- it's the task of the view component to render the array of product
- objects to a HTML page.</para>
- </listitem>
-
- <listitem>
- <para>Controller: The controller handles user input, modifies the
- model, and ensures that the view is updated when needed. For
- example it is the task of controller to take the incoming HTTP
- requests, parse the received parameters (forms), dispatch the
- requests to the proper business logic object, and chose the right
- template for the HTTP response.</para>
- </listitem>
- </itemizedlist>
-
- <para>The most important thing for us when applying MVC for Web
- applications is the separation of View from the other two roles. This
- allows the separation of designers (HTML authors) from programmers.
- Designers deal with the visual aspects, programmers deal with the
- application logic and other technical issues; everybody works on what
- he is good at. Designers and programmers are less dependent on each
- other. Designers can change the appearance without programmers having
- to change or recompile the program.</para>
-
- <para>For more information I recommend reading <link
- xlink:href="http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/web-tier/web-tier5.html">chapter
- 4.4</link> of Designing Enterprise Applications with the J2EE Platform
- blueprint.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.predefinedDirective">
- <glossterm>Predefined directive</glossterm>
-
- <glossdef>
- <para>Directive what is defined by FreeMarker, thus always available.
- Example of predefined directives: <literal>if</literal>,
- <literal>list</literal>, <literal>include</literal></para>
-
- <glossseealso otherterm="gloss.userDefinedDirective"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.scalarVariable">
- <glossterm>Scalar</glossterm>
-
- <glossdef>
- <para>A scalar variable stores a single value. A scalar is either a
- string or a number or a date/time or a <link
- linkend="gloss.boolean">boolean</link>.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.sequenceVariable">
- <glossterm>Sequence</glossterm>
-
- <glossdef>
- <para>A sequence is a variable that contains a sequence of sub
- variables. The sequence's sub variables are accessible via numerical
- index, where the index of the very first object is 0, the index of the
- second objects is 1, the index of the third object is 2, etc.</para>
-
- <glossseealso otherterm="gloss.hashVariable"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.regularExpression">
- <glossterm>Regular expression</glossterm>
-
- <glossdef>
- <para>A regular expression is a string that specifies a set of strings
- that matches it. For example, the regular expression
- <literal>"fo*"</literal> matches <literal>"f"</literal>,
- <literal>"fo"</literal>, <literal>"foo"</literal>, etc. Regular
- expressions are used in several languages and other tools. In
- FreeMarker, the usage of them is a ``power user'' option. So if you
- have never used them before, there is no need to worry about not being
- familiar with them. But if you are interested in regular expressions,
- you can find several Web pages and books about them. FreeMarker uses
- the variation of regular expressions described at: <link
- xlink:href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html</link></para>
- </glossdef>
- </glossentry>
-
- <glossentry>
- <glossterm>SGML</glossterm>
-
- <glosssee otherterm="gloss.SGML"/>
- </glossentry>
-
- <glossentry xml:id="gloss.SGML">
- <glossterm>Standard Generalized Markup Language</glossterm>
-
- <glossdef>
- <para>This is an international standard (ISO 8879) that specifies the
- rules for the creation of platform-independent markup languages. HTML
- is a markup language created with SGML. <link
- linkend="gloss.XML">XML</link> is a subset (restricted version) of
- SGML.</para>
-
- <glossseealso otherterm="gloss.XML">XML</glossseealso>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.string">
- <glossterm>String</glossterm>
-
- <glossdef>
- <para>A sequence of <link linkend="gloss.character">characters</link>
- such as ``m'', ``o'', ``u'', ``s'', ``e''.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.tag">
- <glossterm>Tag</glossterm>
-
- <glossdef>
- <para>Text fragment indicating the usage of an element in SGML.
- Examples of tags: <literal><body bgcolor=black></literal>,
- <literal></body></literal></para>
-
- <glossseealso otherterm="gloss.startTag"/>
-
- <glossseealso otherterm="gloss.endTag"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.template">
- <glossterm>Template</glossterm>
-
- <glossdef>
- <para>A template is a text file with some special character sequences
- embedded into it. A template processor (e.g. FreeMarker) will
- interpret special character sequences and it outputs a more or less
- different text from the original text file, where the differences are
- often based on a <link linkend="gloss.dataModel">data-model</link>.
- Thus, the original text acts as a template of the possible
- outputs.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.templateProcessingJob">
- <glossterm>Template processing job</glossterm>
-
- <glossdef>
- <para>A template processing job is the act when FreeMarker merges a
- template with a data-model to produce the output for a visitor. Note
- that this may includes the execution of multiple template files
- because the template file used for the Web page may invokes other
- templates with <literal>include</literal> and
- <literal>import</literal> directives. Each template-processing job is
- a separated cosmos that exists only for the short period of time while
- the given page is being rendered for the visitor, and then it vanishes
- with all the variables created in the templates (for example,
- variables created with <literal>assign</literal>,
- <literal>macro</literal> or <literal>global</literal>
- directives).</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.transformVariable">
- <glossterm>Transform</glossterm>
-
- <glossdef>
- <para>This term refers to user-defined directives that are implemetned
- with the now obsolete <literal>TemplateTransformModel</literal> Java
- interface. The feature was originally made for implementing output
- filters, hence the name.</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.threadSafe">
- <glossterm>Thread-safe</glossterm>
-
- <glossdef>
- <para>An object is thread-safe if it is safe to call its methods from
- multiple threads, even in parallel (i.e. multiple threads execute the
- methods of the object at the same time). Non-thread-safe objects may
- behave unpredictably in this situation, and generate wrong results,
- corrupt internal data structures, etc. Thread-safety is typically
- achieved in two ways with Java: with the usage
- <literal>synchronized</literal> statement (or
- <literal>synchronized</literal> methods), and with the immutability of
- encapsulated data (i.e. you can't modify the field after you have
- created the object).</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.userDefinedDirective">
- <glossterm>User-defined directive</glossterm>
-
- <glossdef>
- <para>Directive that is not defined by the FreeMarker core, but by the
- user. These are typically application domain specific directives, like
- pull-down menu generation directives, HTML form handling
- directives.</para>
-
- <glossseealso otherterm="gloss.predefinedDirective"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.UCS">
- <glossterm>UCS</glossterm>
-
- <glossdef>
- <para>This is international standard (ISO-10646) that defines a huge
- set of <link linkend="gloss.character">characters</link> and assigns a
- unique number for each character (``!'' is 33, ..., ``A'' is 61, ``B''
- is 62, ..., Arabic letter hamza is 1569... etc.). This character set
- (not charset) contains almost all characters used today (Latin
- alphabet, Cyrillic alphabet, Chinese letters, etc.). The idea behind
- UCS is that we can specify any character with a unique number, not
- mater what the platform or the language is.</para>
-
- <glossseealso otherterm="gloss.unicode"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.unicode">
- <glossterm>Unicode</glossterm>
-
- <glossdef>
- <para>De-facto standard developed by Unicode organization. It deals
- with the classification of the characters in <link
- linkend="gloss.UCS">UCS</link> (which is letter, which is digit, which
- is uppercase, which is lowercase, etc.), and with other problems of
- processing text made from the characters of UCS (e.g.
- normalization).</para>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.whiteSpace">
- <glossterm>White-space(空白)</glossterm>
-
- <glossdef>
- <para>完全透明的字符,但是对文本的视觉呈现会有作用。
- 空白字符的示例:空格,制表符(水平和垂直),换行符(CR和LF),换页。</para>
-
- <glossseealso otherterm="gloss.lineBreak"/>
- </glossdef>
- </glossentry>
-
- <glossentry>
- <glossterm>XML</glossterm>
-
- <glosssee otherterm="gloss.XML"/>
- </glossentry>
-
- <glossentry xml:id="gloss.attribute">
- <glossterm>Attribute(属性)</glossterm>
-
- <glossdef>
- <para>为了连接 <link linkend="gloss.XML">XML</link> 或HTML
- (或通用的 <link linkend="gloss.SGML">SGML</link>),属性是关联元素的命名的值。
- 比如,在 <literal><body bgcolor=black
- text=green><replaceable>...</replaceable></body></literal>中,
- 属性是 <literal>bgcolor=black</literal> 和 <literal>text=green</literal>。
- 在符号 <literal>=</literal> 的左边是属性的名字,而在右边的是属性的值。
- 注意在XML中,值必须是被引号引起来的(比如:<literal><body bgcolor="black"
- text='green'></literal>),而在HTML中它对某些值是可选的。</para>
-
- <glossseealso otherterm="gloss.startTag"/>
- </glossdef>
- </glossentry>
-
- <glossentry xml:id="gloss.fullQualifiedName">
- <glossterm>Full-qualified name(完全限定名)</glossterm>
-
- <glossdef>
- <para>... 对于结点来说(XML结点或其它FTL结点变量):
- 结点的完全限定名不仅仅指定结点名称
- (<literal><replaceable>node</replaceable>?node_name</literal>),
- 而且指定结点的命名空间
- (<literal><replaceable>node</replaceable>?node_namespace</literal>),
- 这种方式明确地指出结点的确定类型。完全限定名的格式是
- <literal><replaceable>nodeName</replaceable></literal> 或
- <literal><replaceable>prefix</replaceable>:<replaceable>nodeName</replaceable></literal>。
- 前缀是识别结点命名空间(结点的命名空间通常是使用很长的URI来指定的)的速记形式。
- 在FTL中,前缀使用<link linkend="ref.directive.ftl"><literal>ftl</literal>
- 指令</link> 的 <literal>ns_prefixes</literal> 参数和结点的命名空间相关联。
- 在XML文件中,前缀使用 <literal>xmlns:<replaceable>prefix</replaceable></literal>
- 属性和结点的命名空间相关联。如果默认的命名空间被定义了,
- 缺少前缀说明结点使用默认的命名空间;否则就说明结点不属于任何结点命名空间。
- FTL中定义的默认结点命名空间通过使用 <literal>ftl</literal> 指令
- 的 <literal>ns_prefixes</literal> 参数来注册保留前缀 <literal>D</literal>。
- 在XML文件中,使用属性 <literal>xmlns</literal> 来定义。</para>
-
- <para>... 对于Java类来说:Java类的完全限定名包含类名和所属包的名字。
- 这种方式明确地指出了类,而不管其内容。类的完全限定名示例:
- <literal>java.util.Map</literal> (和 <literal>Map</literal>相对)。</para>
- </glossdef>
- </glossentry>
- </glossary>
-
- <index xml:id="alphaidx">
- <title>字母顺序索引</title>
- </index>
-</book>
diff --git a/src/manual/zh_CN/docgen-help/README b/src/manual/zh_CN/docgen-help/README
deleted file mode 100644
index 6ebc928..0000000
--- a/src/manual/zh_CN/docgen-help/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Put the locale-specific or translated guides to editors here.
-For the non-localized guides see the similar folder of the en_US Manual.
\ No newline at end of file
diff --git a/src/manual/zh_CN/docgen-misc/googleAnalytics.html b/src/manual/zh_CN/docgen-misc/googleAnalytics.html
deleted file mode 100644
index 759564e..0000000
--- a/src/manual/zh_CN/docgen-misc/googleAnalytics.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!--
- This snippet was generated by Google Analytics.
- Thus, the standard FreeMarker copyright comment was intentionally omitted.
- <#DO_NOT_UPDATE_COPYRIGHT>
--->
-<script>
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
- ga('create', 'UA-55420501-1', 'auto');
- ga('send', 'pageview');
-</script>
diff --git a/src/manual/zh_CN/docgen-originals/figures/README b/src/manual/zh_CN/docgen-originals/figures/README
deleted file mode 100644
index f3a8221..0000000
--- a/src/manual/zh_CN/docgen-originals/figures/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Put the translated originals (sources) of the figures used in the manual here.
-For figures that aren't translated, see the similar folder of the en_US Manual.
\ No newline at end of file
diff --git a/src/manual/zh_CN/docgen.cjson b/src/manual/zh_CN/docgen.cjson
deleted file mode 100644
index d11f3b1..0000000
--- a/src/manual/zh_CN/docgen.cjson
+++ /dev/null
@@ -1,132 +0,0 @@
-//charset: UTF-8
-
-// 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.
-
-deployUrl: "https://freemarker.apache.org/docs/"
-onlineTrackerHTML: "docgen-misc/googleAnalytics.html"
-cookieConsentScriptURL: "https://cdn.jsdelivr.net/npm/cookie-bar/cookiebar-latest.min.js"
-searchKey: "014728049242975963158:8awjt03uofm"
-validation: {
- programlistingsRequireRole
- // programlistingsRequireLanguage
- maximumProgramlistingWidth: 100
-}
-showXXELogo
-generateEclipseTOC
-// eclipse: {
-// link_to: "freemarker-toc.xml#ManualLink"
-// }
-
-removeNodesWhenOnline: [ "preface" ]
-
-copyrightHolder: "The Apache Software Foundation"
-copyrightStartYear: 1999
-copyrightCommentFile: "../en_US/docgen-misc/copyrightComment.txt"
-
-seoMeta: {
- "dgui_quickstart": {
- "title": "Getting Started with template writing"
- }
- "pgui_quickstart": {
- "title": "Getting Started with the Java API"
- }
-}
-
-logo: {
- href: "https://freemarker.apache.org"
- src: logo.png,
- alt: "FreeMarker"
-}
-
-olinks: {
- homepage: "https://freemarker.apache.org/"
- api: "api/index.html"
-
- // Homepage links:
- freemarkerdownload: "https://freemarker.apache.org/freemarkerdownload.html"
- contribute: "https://freemarker.apache.org/contribute.html"
- history: "https://freemarker.apache.org/history.html"
- what-is-freemarker: "https://freemarker.apache.org/"
- mailing-lists: "https://freemarker.apache.org/mailing-lists.html"
-
- // External URL-s:
- onlineTemplateTester: "http://try.freemarker.org/"
- twitter: "https://twitter.com/freemarker"
- sourceforgeProject: "https://sourceforge.net/projects/freemarker/"
- githubProject: "https://github.com/freemarker/freemarker"
- newBugReport: "https://sourceforge.net/p/freemarker/bugs/new/"
- newStackOverflowQuestion: "https://stackoverflow.com/questions/ask?tags=freemarker"
- asfIncubator: "http://incubator.apache.org/"
-}
-
-internalBookmarks: {
- "Alpha. index": alphaidx
- "Glossary": gloss
- "Expressions": exp_cheatsheet
- "?builtins": ref_builtins_alphaidx
- "#directives": ref_directive_alphaidx
- ".spec_vars": ref_specvar
- "FAQ": app_faq
-}
-
-tabs: {
- "Home": "olink:homepage"
- "Manual": "" // Empty => We are here
- "Java API": "olink:api"
-}
-
-// Available icons:
-// .icon-heart
-// .icon-bug
-// .icon-download
-// .icon-star
-secondaryTabs: {
- "Contribute": { class: "icon-heart", href: "olink:contribute" }
- "Report a Bug": { class: "icon-bug", href: "olink:newBugReport" }
- "Download": { class: "icon-download", href: "olink:freemarkerdownload" }
-}
-
-footerSiteMap: {
- "Overview": {
- "What is FreeMarker?": "olink:what-is-freemarker"
- "Download": "olink:freemarkerdownload"
- "Version history": "id:app_versions"
- "About us": "olink:history"
- "License": "id:app_license"
- }
- "Handy stuff": {
- "Try template online": "olink:onlineTemplateTester"
- "Expressions cheatsheet": "id:exp_cheatsheet"
- "#directives": "id:ref_directive_alphaidx"
- "?built_ins": "id:ref_builtins_alphaidx"
- ".special_vars": "id:ref_specvar"
- }
- "Community": {
- "FreeMarker on Github": "olink:githubProject"
- "Follow us on Twitter": "olink:twitter"
- "Report a bug": "olink:newBugReport"
- "Ask a question": "olink:newStackOverflowQuestion"
- "Mailing lists": "olink:mailing-lists"
- }
-}
-
-socialLinks: {
- "Github": { class: "github", href: "olink:githubProject" }
- "Twitter": { class: "twitter", href: "olink:twitter" }
- "Stack Overflow": { class: "stack-overflow", href: "olink:newStackOverflowQuestion" }
-}
diff --git a/src/manual/zh_CN/favicon.png b/src/manual/zh_CN/favicon.png
deleted file mode 100644
index ce0de20..0000000
--- a/src/manual/zh_CN/favicon.png
+++ /dev/null
Binary files differ
diff --git a/src/manual/zh_CN/figures/model2sketch.png b/src/manual/zh_CN/figures/model2sketch.png
deleted file mode 100644
index 93f9a6b..0000000
--- a/src/manual/zh_CN/figures/model2sketch.png
+++ /dev/null
Binary files differ
diff --git a/src/manual/zh_CN/figures/overview.png b/src/manual/zh_CN/figures/overview.png
deleted file mode 100644
index b32e0bd..0000000
--- a/src/manual/zh_CN/figures/overview.png
+++ /dev/null
Binary files differ
diff --git a/src/manual/zh_CN/figures/tree.png b/src/manual/zh_CN/figures/tree.png
deleted file mode 100644
index dcd9bf3..0000000
--- a/src/manual/zh_CN/figures/tree.png
+++ /dev/null
Binary files differ
diff --git a/src/manual/zh_CN/logo.png b/src/manual/zh_CN/logo.png
deleted file mode 100644
index 193dc11..0000000
--- a/src/manual/zh_CN/logo.png
+++ /dev/null
Binary files differ
diff --git a/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java b/src/test/java/freemarker/core/ArithmeticEngineTest.java
similarity index 74%
rename from src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java
rename to src/test/java/freemarker/core/ArithmeticEngineTest.java
index d6db2f7..fc22d58 100644
--- a/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java
+++ b/src/test/java/freemarker/core/ArithmeticEngineTest.java
@@ -25,9 +25,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import org.hamcrest.Matchers;
import org.junit.Test;
-public class BigDecimalArithmeticEngineTest {
+public class ArithmeticEngineTest {
@Test
public void compareNumbersZeroTest() {
@@ -94,4 +95,32 @@
}
}
+ @Test
+ public void toNumberTest() {
+ for (ArithmeticEngine arithmeticEngine : new ArithmeticEngine[]{BIGDECIMAL_ENGINE, CONSERVATIVE_ENGINE}) {
+ assertEquals(Double.POSITIVE_INFINITY, arithmeticEngine.toNumber("INF"));
+ assertEquals(Double.NEGATIVE_INFINITY, arithmeticEngine.toNumber("-INF"));
+ assertEquals(Double.NEGATIVE_INFINITY, arithmeticEngine.toNumber("-Infinity"));
+ assertEquals(Double.POSITIVE_INFINITY, arithmeticEngine.toNumber("Infinity"));
+ Number nan = arithmeticEngine.toNumber("NaN");
+ assertThat(nan, Matchers.instanceOf(Double.class));
+ assertTrue(Double.isNaN((double) nan));
+ }
+
+ assertEquals(new BigDecimal("1234567"), BIGDECIMAL_ENGINE.toNumber("1234567"));
+ assertEquals(Integer.valueOf("1234567"), CONSERVATIVE_ENGINE.toNumber("1234567"));
+
+ assertEquals(new BigDecimal("12345678901234"), BIGDECIMAL_ENGINE.toNumber("12345678901234"));
+ assertEquals(12345678901234L, CONSERVATIVE_ENGINE.toNumber("12345678901234"));
+
+ assertEquals(new BigDecimal("12345678901234567890"), BIGDECIMAL_ENGINE.toNumber("12345678901234567890"));
+ assertEquals(new BigInteger("12345678901234567890"), CONSERVATIVE_ENGINE.toNumber("12345678901234567890"));
+
+ assertEquals(new BigDecimal("1.9"), BIGDECIMAL_ENGINE.toNumber("1.9"));
+ assertEquals(1.9, CONSERVATIVE_ENGINE.toNumber("1.9"));
+
+ assertEquals(new BigDecimal("0.9"), BIGDECIMAL_ENGINE.toNumber(".9"));
+ assertEquals(0.9, CONSERVATIVE_ENGINE.toNumber(".9"));
+ }
+
}
diff --git a/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java b/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java
new file mode 100644
index 0000000..0feb90c
--- /dev/null
+++ b/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class BooleanFormatEnvironmentCachingTest extends TemplateTest {
+
+ @Override
+ protected Configuration createConfiguration() throws Exception {
+ Configuration conf = super.createConfiguration();
+ conf.setCFormat(CustomCFormat.INSTANCE);
+ conf.setBooleanFormat("c");
+ return conf;
+ }
+
+ @Test
+ public void test() throws TemplateException, IOException {
+ assertOutput(
+ ""
+ + "${true} ${true} ${false} ${false} "
+ + "<#setting cFormat='JSON'>${true} ${true} ${false} ${false} "
+ + "<#setting booleanFormat='y,n'>${true} ${true} ${false} ${false} "
+ + "<#setting cFormat='Java'>${true} ${true} ${false} ${false} "
+ + "<#setting booleanFormat='c'>${true} ${true} ${false} ${false}",
+ ""
+ + "TRUE TRUE FALSE FALSE "
+ + "true true false false "
+ + "y y n n "
+ + "y y n n "
+ + "true true false false");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/core/CAndCnBuiltInTest.java b/src/test/java/freemarker/core/CAndCnBuiltInTest.java
new file mode 100644
index 0000000..9e73e8a
--- /dev/null
+++ b/src/test/java/freemarker/core/CAndCnBuiltInTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Timestamp;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.Version;
+import freemarker.template._VersionInts;
+import freemarker.test.TemplateTest;
+
+public class CAndCnBuiltInTest extends TemplateTest {
+
+ @Before
+ public void addModelVariables() {
+ addToDataModel("double1", 1.0);
+ addToDataModel("double2", 1.000000000000001);
+ addToDataModel("double3", 0.0000000000000001);
+ addToDataModel("double4", -0.0000000000000001);
+ addToDataModel("bigDecimal1", new BigDecimal("1"));
+ addToDataModel("bigDecimal2", new BigDecimal("0.0000000000000001"));
+ addToDataModel("doubleInf", Double.POSITIVE_INFINITY);
+ addToDataModel("doubleNegativeInf", Double.NEGATIVE_INFINITY);
+ addToDataModel("doubleNaN", Double.NaN);
+ addToDataModel("floatInf", Float.POSITIVE_INFINITY);
+ addToDataModel("floatNegativeInf", Float.NEGATIVE_INFINITY);
+ addToDataModel("floatNaN", Float.NaN);
+ addToDataModel("string", "a\nb\u0000c");
+ addToDataModel("long", Long.MAX_VALUE);
+ addToDataModel("int", Integer.MAX_VALUE);
+ addToDataModel("bigInteger", new BigInteger("123456789123456789123456789123456789"));
+ addToDataModel("dateTime", new Timestamp(1671641049876L));
+ addToDataModel("booleanTrue", true);
+ addToDataModel("booleanFalse", false);
+ }
+
+ @Test
+ public void testCWithNumber() throws TemplateException, IOException {
+ testWithNumber("c");
+ }
+
+ @Test
+ public void testCnWithNumber() throws TemplateException, IOException {
+ testWithNumber("cn");
+ }
+
+ void testWithNumber(String builtInName) throws TemplateException, IOException {
+ testWithNumber(builtInName, Configuration.VERSION_2_3_20);
+ testWithNumber(builtInName, Configuration.VERSION_2_3_21);
+ testWithNumber(builtInName, Configuration.VERSION_2_3_31);
+ testWithNumber(builtInName, Configuration.VERSION_2_3_32);
+ }
+
+ void testWithNumber(String builtInName, Version ici) throws TemplateException, IOException {
+ // Always the same
+ assertOutput("${double1? " + builtInName + "}", "1");
+ assertOutput("${double2?" + builtInName + "}", "1.000000000000001");
+ assertOutput("${bigDecimal1? " + builtInName + "}", "1");
+ assertOutput("${int? " + builtInName + "}", String.valueOf(Integer.MAX_VALUE));
+ assertOutput("${long? " + builtInName + "}", String.valueOf(Long.MAX_VALUE));
+ assertOutput("${bigInteger? " + builtInName + "}", "123456789123456789123456789123456789");
+
+ getConfiguration().setIncompatibleImprovements(ici);
+
+ if (ici.intValue() >= _VersionInts.V_2_3_32) {
+ assertOutput("${double3?" + builtInName + "}", "1E-16");
+ assertOutput("${double4?" + builtInName + "}", "-1E-16");
+ assertOutput("${bigDecimal2?" + builtInName + "}", "1E-16");
+ } else {
+ assertOutput("${double3?" + builtInName + "}", "0.0000000000000001");
+ assertOutput("${double4?" + builtInName + "}", "-0.0000000000000001");
+ assertOutput("${bigDecimal2?" + builtInName + "}", "0.0000000000000001");
+ }
+
+ for (String type : new String[] {"float", "double"}) {
+ String expectedInf;
+ String expectedNaN;
+ if (ici.intValue() >= _VersionInts.V_2_3_32) {
+ expectedInf = "Infinity";
+ expectedNaN = "NaN";
+ } else if (ici.intValue() >= _VersionInts.V_2_3_21) {
+ expectedInf = "INF";
+ expectedNaN = "NaN";
+ } else {
+ expectedInf = "\u221E";
+ expectedNaN = "\uFFFD";
+ }
+
+ assertOutput("${" + type + "Inf?" + builtInName + "}", expectedInf);
+ assertOutput("${" + type + "NegativeInf?" + builtInName + "}", "-" + expectedInf);
+ assertOutput("${" + type + "NaN?" + builtInName + "}", expectedNaN);
+ }
+ }
+
+ @Test
+ public void testWithNonNumber() throws TemplateException, IOException {
+ for (Version ici : new Version[] {
+ Configuration.VERSION_2_3_0, Configuration.VERSION_2_3_31, Configuration.VERSION_2_3_32 }) {
+ testWithNonNumber("c", ici);
+ testWithNonNumber("cn", ici);
+ }
+ }
+
+ private void testWithNonNumber(String builtInName, Version ici) throws TemplateException, IOException {
+ getConfiguration().setIncompatibleImprovements(ici);
+ assertOutput("${string?" + builtInName + "}", "\"a\\nb\\u0000c\"");
+ assertOutput("${booleanTrue?" + builtInName + "}", "true");
+ assertOutput("${booleanFalse?" + builtInName + "}", "false");
+ assertErrorContains("${dateTime?" + builtInName + "}",
+ "Expected a number, boolean, or string");
+ }
+
+ @Test
+ public void testCFormatsWithString() throws TemplateException, IOException {
+ Configuration conf = getConfiguration();
+ conf.setCFormat(JavaScriptCFormat.INSTANCE);
+ assertOutput("${string?c}", "\"a\\nb\\x00c\"");
+ conf.setCFormat(JSONCFormat.INSTANCE);
+ assertOutput("${string?c}", "\"a\\nb\\u0000c\"");
+ conf.setCFormat(JavaScriptOrJSONCFormat.INSTANCE);
+ assertOutput("${string?c}", "\"a\\nb\\u0000c\"");
+ conf.setCFormat(XSCFormat.INSTANCE);
+ assertOutput("${string?c}", "a\nb\u0000c");
+ }
+
+ @Test
+ public void testWithNull() throws TemplateException, IOException {
+ assertOutput("${noSuchVar?cn}", "null");
+ assertErrorContains("${noSuchVar?c}", "null or missing");
+ }
+
+}
diff --git a/src/test/java/freemarker/core/CFormatTemplateTest.java b/src/test/java/freemarker/core/CFormatTemplateTest.java
new file mode 100644
index 0000000..fd8a66f
--- /dev/null
+++ b/src/test/java/freemarker/core/CFormatTemplateTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class CFormatTemplateTest extends TemplateTest {
+
+ @Before
+ public void addDataModelVariables() {
+ addToDataModel("s", "a'b\"c\u0001");
+ }
+
+ @Test
+ public void testBooleanAndNullFormat() throws TemplateException, IOException {
+ getConfiguration().setCFormat(CustomCFormat.INSTANCE);
+ assertOutput(
+ ""
+ + "${true?c} ${false?c} ${null?cn} "
+ + "JSON: <#setting c_format='JSON'>${true?c} ${false?c} ${null?cn}",
+ ""
+ + "TRUE FALSE NULL "
+ + "JSON: true false null");
+ assertOutput(
+ "<#setting boolean_format='c'>"
+ + "${true} ${false} "
+ + "JSON: <#setting c_format='JSON'>${true} ${false}",
+ ""
+ + "TRUE FALSE "
+ + "JSON: true false");
+ }
+
+ @Test
+ public void testStringFormat() throws TemplateException, IOException {
+ assertOutput(
+ ""
+ + "Default: ${s?c} "
+ + "XS: <#setting c_format='XS'>${s?c} "
+ + "JavaScript: <#setting c_format='JavaScript'>${s?c} "
+ + "JSON: <#setting c_format='JSON'>${s?c} "
+ + "Java: <#setting c_format='Java'>${s?c} ",
+ ""
+ + "Default: \"a'b\\\"c\\u0001\" "
+ + "XS: a'b\"c\u0001 "
+ + "JavaScript: \"a'b\\\"c\\x01\" "
+ + "JSON: \"a'b\\\"c\\u0001\" "
+ + "Java: \"a'b\\\"c\\u0001\" ");
+ }
+
+ @Test
+ public void testUnsafeSetting() throws TemplateException, IOException {
+ assertErrorContains("<#setting c_format='com.example.ExploitCFormat()'>", "not allowed");
+ assertErrorContains("<#setting cFormat='com.example.ExploitCFormat()'>", "not allowed");
+ }
+
+}
diff --git a/src/test/java/freemarker/core/CTemplateNumberFormatTest.java b/src/test/java/freemarker/core/CTemplateNumberFormatTest.java
new file mode 100644
index 0000000..081f21a
--- /dev/null
+++ b/src/test/java/freemarker/core/CTemplateNumberFormatTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.core;
+
+import static org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.junit.Test;
+
+import freemarker.template.SimpleNumber;
+import freemarker.template.TemplateModelException;
+import freemarker.template.utility.UnsupportedNumberClassException;
+
+public class CTemplateNumberFormatTest {
+ @Test
+ public void testFormatDouble() throws TemplateModelException, TemplateValueFormatException {
+ testFormat(1.0, "1");
+ testFormat(1.2, "1.2");
+ testFormat(9007199254740991d, "9007199254740991");
+ testFormat(9007199254740992d, "9007199254740992");
+ testFormat(9007199254740994d, "9.007199254740994E15");
+ testFormat(10000000000000000d, "1E16");
+ testFormat(12300000000000000d, "1.23E16");
+ testFormat(Double.NaN, "NaN");
+ testFormat(Double.POSITIVE_INFINITY, "Infinity");
+ testFormat(Double.NEGATIVE_INFINITY, "-Infinity");
+ testFormat(1.9E-6, "0.0000019");
+ testFormat(9.5E-7, "9.5E-7");
+ testFormat(9999999.5, "9999999.5");
+ testFormat(10000000.5, "10000000.5");
+ }
+
+ @Test
+ public void testFormatFloat() throws TemplateModelException, TemplateValueFormatException {
+ testFormat(1.0f, "1");
+ testFormat(1.2f, "1.2");
+ testFormat(16777215f, "16777215");
+ testFormat(16777216f, "16777216");
+ testFormat(16777218f, "1.6777218E7");
+ testFormat(100000000f, "1E8");
+ testFormat(123000000f, "1.23E8");
+ testFormat(Float.NaN, "NaN");
+ testFormat(Float.POSITIVE_INFINITY, "Infinity");
+ testFormat(Float.NEGATIVE_INFINITY, "-Infinity");
+ testFormat(1.9E-6f, "0.0000019");
+ testFormat(9.5E-7f, "9.5E-7");
+ testFormat(1000000.5f, "1000000.5");
+ // For float, values >= 1E7 has ulp >= 1, so we don't have to deal with non-wholes in that range.
+ }
+
+ @Test
+ public void testFormatBigInteger() throws TemplateModelException, TemplateValueFormatException {
+ testFormat(new BigInteger("-0"), "0");
+ testFormat(new BigInteger("1"), "1");
+ testFormat(new BigInteger("9000000000000000000000"), "9000000000000000000000");
+ }
+
+ @Test
+ public void testFormatBigDecimalWholeNumbers() throws TemplateModelException, TemplateValueFormatException {
+ testFormat(new BigDecimal("-0"), "0");
+ testFormat(new BigDecimal("1.0"), "1");
+ testFormat(new BigDecimal("10E-1"), "1");
+ testFormat(new BigDecimal("0.01E2"), "1");
+ testFormat(new BigDecimal("9000000000000000000000"), "9000000000000000000000");
+ testFormat(new BigDecimal("9e21"), "9000000000000000000000");
+ testFormat(new BigDecimal("9e100"), "9E+100");
+ }
+
+ @Test
+ public void testFormatBigDecimalNonWholeNumbers() throws TemplateModelException, TemplateValueFormatException {
+ testFormat(new BigDecimal("0.1"), "0.1");
+ testFormat(new BigDecimal("1E-1"), "0.1");
+ testFormat(new BigDecimal("0.0000001"), "1E-7");
+ testFormat(new BigDecimal("0.00000010"), "1E-7");
+ testFormat(new BigDecimal("0.000000999"), "9.99E-7");
+ testFormat(new BigDecimal("-0.0000001"), "-1E-7");
+ testFormat(new BigDecimal("0.000000123"), "1.23E-7");
+ testFormat(new BigDecimal("1E-6"), "0.000001");
+ testFormat(new BigDecimal("0.0000010"), "0.000001");
+ testFormat(new BigDecimal("1.0000000001"), "1.0000000001");
+ testFormat(new BigDecimal("1000000000.5"), "1000000000.5");
+ }
+
+ private void testFormat(Number n, String expectedResult) throws TemplateModelException,
+ TemplateValueFormatException {
+ TemplateNumberFormat cTemplateNumberFormat = JSONCFormat.INSTANCE.getTemplateNumberFormat(null);
+ String actualResult = (String) cTemplateNumberFormat.format(new SimpleNumber(n));
+ assertFormatResult(n, actualResult, expectedResult);
+ if (!actualResult.equals("NaN") && !actualResult.equals("0") && !actualResult.startsWith("-")) {
+ Number negativeN = negate(n);
+ actualResult = (String) cTemplateNumberFormat.format(new SimpleNumber(negativeN));
+ assertFormatResult(negativeN, actualResult, "-" + expectedResult);
+ }
+ }
+
+ private static Number negate(Number n) {
+ if (n instanceof Integer) {
+ return -n.intValue();
+ } else if (n instanceof BigDecimal) {
+ return ((BigDecimal) n).negate();
+ } else if (n instanceof Double) {
+ return -n.doubleValue();
+ } else if (n instanceof Float) {
+ return -n.floatValue();
+ } else if (n instanceof Long) {
+ return -n.longValue();
+ } else if (n instanceof Short) {
+ return -n.shortValue();
+ } else if (n instanceof Byte) {
+ return -n.byteValue();
+ } else if (n instanceof BigInteger) {
+ return ((BigInteger) n).negate();
+ } else {
+ throw new UnsupportedNumberClassException(n.getClass());
+ }
+ }
+
+ private void assertFormatResult(Number n, String actualResult, String expectedResult) {
+ if (!actualResult.equals(expectedResult)) {
+ fail("When formatting " + n + ", expected \"" + expectedResult + "\", but got "
+ + "\"" + actualResult + "\".");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/core/CustomCFormat.java b/src/test/java/freemarker/core/CustomCFormat.java
new file mode 100644
index 0000000..9ec224b
--- /dev/null
+++ b/src/test/java/freemarker/core/CustomCFormat.java
@@ -0,0 +1,83 @@
+/*
+ * 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.core;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+import freemarker.template.TemplateException;
+import freemarker.template.utility.StringUtil;
+
+class CustomCFormat extends CFormat {
+ final static CustomCFormat INSTANCE = new CustomCFormat();
+
+ private CustomCFormat() {
+ }
+
+ private static final TemplateNumberFormat TEMPLATE_NUMBER_FORMAT = new CTemplateNumberFormat(
+ "M:INF", "M:NINF", "M:NaN",
+ "M:INF", "M:NINF", "M:NaN");
+
+ private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+ = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+
+ static {
+ DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
+ symbols.setInfinity("Infinity");
+ symbols.setNaN("NaN");
+ LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalFormatSymbols(symbols);
+ }
+
+ @Override
+ TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+ return TEMPLATE_NUMBER_FORMAT;
+ }
+
+ @Override
+ NumberFormat getLegacyNumberFormat(Environment env) {
+ return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+ }
+
+ @Override
+ String formatString(String s, Environment env) throws TemplateException {
+ return StringUtil.ftlQuote(s);
+ }
+
+ @Override
+ String getTrueString() {
+ return "TRUE";
+ }
+
+ @Override
+ String getFalseString() {
+ return "FALSE";
+ }
+
+ @Override
+ String getNullString() {
+ return "NULL";
+ }
+
+ @Override
+ public String getName() {
+ return "custom";
+ }
+}
diff --git a/src/test/java/freemarker/core/NumberBiTest.java b/src/test/java/freemarker/core/NumberBiTest.java
new file mode 100644
index 0000000..e4c1a8c
--- /dev/null
+++ b/src/test/java/freemarker/core/NumberBiTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class NumberBiTest extends TemplateTest {
+ @Override
+ protected Configuration createConfiguration() throws Exception {
+ Configuration conf = super.createConfiguration();
+ conf.setIncompatibleImprovements(Configuration.VERSION_2_3_21);
+ return conf;
+ }
+
+ @Test
+ public void testSimple() throws TemplateException, IOException {
+ assertNumberBi("1", "1");
+ assertNumberBi("-1", "-1");
+ assertNumberBi("1.9000", "1.9");
+ assertNumberBi("19E-1", "1.9");
+ assertNumberBi("INF", "INF");
+ assertNumberBi("-Infinity", "-INF");
+ assertNumberBi("NaN", "NaN");
+ }
+
+ @Test
+ public void testPlusPrefix() throws TemplateException, IOException {
+ assertNumberBi("+1", "1");
+ }
+
+ private final void assertNumberBi(String input, String output) throws TemplateException, IOException {
+ assertOutput("${'" + input + "'?number?c}", output);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 184fc21..4d4ce23 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -46,7 +46,6 @@
import freemarker.template.TemplateNumberModel;
import freemarker.template.Version;
import freemarker.test.TemplateTest;
-import net.jcip.annotations.Immutable;
@SuppressWarnings("boxing")
public class NumberFormatTest extends TemplateTest {
@@ -326,14 +325,16 @@
for (Version ici : new Version[] {
Configuration.VERSION_2_3_20,
Configuration.VERSION_2_3_21, Configuration.VERSION_2_3_30,
- Configuration.VERSION_2_3_31 } ) {
+ Configuration.VERSION_2_3_31,
+ Configuration.VERSION_2_3_32 } ) {
cfg.setIncompatibleImprovements(ici);
boolean cBuiltInBroken = ici.intValue() < Configuration.VERSION_2_3_21.intValue();
boolean cNumberFormatBroken = ici.intValue() < Configuration.VERSION_2_3_31.intValue();
String humanAudienceOutput = "\u221e -\u221e \ufffd";
- String computerAudienceOutput = "INF -INF NaN";
+ String computerAudienceOutput = ici.intValue() < Configuration.VERSION_2_3_32.intValue()
+ ? "INF -INF NaN" : "Infinity -Infinity NaN";
assertOutput(
"${pInf?c} ${nInf?c} ${nan?c}",
diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java
index e48c38a..ef4be2b 100644
--- a/src/test/java/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java
@@ -164,6 +164,7 @@
SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD");
SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm");
SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE);
+ SETTING_ASSIGNMENTS.put("CFormat", JavaScriptCFormat.INSTANCE);
SETTING_ASSIGNMENTS.put("logTemplateExceptions", false);
SETTING_ASSIGNMENTS.put("wrapUncheckedExceptions", true);
SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 9bf0609..72287e6 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -54,6 +54,7 @@
import freemarker.cache.TemplateLookupStrategy;
import freemarker.cache.TemplateNameFormat;
import freemarker.core.BaseNTemplateNumberFormatFactory;
+import freemarker.core.CFormat;
import freemarker.core.CombinedMarkupOutputFormat;
import freemarker.core.Configurable;
import freemarker.core.Configurable.SettingValueAssignmentException;
@@ -67,6 +68,11 @@
import freemarker.core.EpochMillisTemplateDateFormatFactory;
import freemarker.core.HTMLOutputFormat;
import freemarker.core.HexTemplateNumberFormatFactory;
+import freemarker.core.JSONCFormat;
+import freemarker.core.JavaCFormat;
+import freemarker.core.JavaScriptCFormat;
+import freemarker.core.JavaScriptOrJSONCFormat;
+import freemarker.core.LegacyCFormat;
import freemarker.core.MarkupOutputFormat;
import freemarker.core.OptInTemplateClassResolver;
import freemarker.core.OutputFormat;
@@ -79,6 +85,7 @@
import freemarker.core.UnregisteredOutputFormatException;
import freemarker.core.XHTMLOutputFormat;
import freemarker.core.XMLOutputFormat;
+import freemarker.core.XSCFormat;
import freemarker.core._CoreStringUtils;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.ext.beans.LegacyDefaultMemberAccessPolicy;
@@ -96,7 +103,7 @@
public ConfigurationTest(String name) {
super(name);
}
-
+
public void testIncompatibleImprovementsChangesDefaults() {
Version newVersion = Configuration.VERSION_2_3_21;
Version oldVersion = Configuration.VERSION_2_3_20;
@@ -180,6 +187,20 @@
cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_27);
assertTrue(((DefaultObjectWrapper) cfg.getObjectWrapper()).getTreatDefaultMethodsAsBeanMembers());
assertFalse(((DefaultObjectWrapper) cfg.getObjectWrapper()).getPreferIndexedReadMethod());
+
+ cfg = new Configuration(Configuration.VERSION_2_3_0);
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_20);
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_21);
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_31);
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_32);
+ assertSame(JavaScriptOrJSONCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setCFormat(JavaScriptOrJSONCFormat.INSTANCE); // Same as default, but explicitly set now
+ cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_31);
+ assertSame(JavaScriptOrJSONCFormat.INSTANCE, cfg.getCFormat());
}
private void assertUses2322ObjectWrapper(Configuration cfg) {
@@ -286,6 +307,14 @@
assertFalse(cfg.isCacheStorageExplicitlySet());
assertTrue(cfg.getCacheStorage() instanceof SoftCacheStorage);
}
+
+ assertFalse(cfg.isCFormatExplicitlySet());
+ //
+ cfg.setCFormat(XSCFormat.INSTANCE);
+ assertTrue(cfg.isCFormatExplicitlySet());
+ //
+ cfg.unsetCFormat();
+ assertFalse(cfg.isCFormatExplicitlySet());
}
public void testTemplateLoadingErrors() throws Exception {
@@ -334,7 +363,7 @@
public void testVersion() {
Version v = Configuration.getVersion();
- assertTrue(v.intValue() > _TemplateAPI.VERSION_INT_2_3_20);
+ assertTrue(v.intValue() > _VersionInts.V_2_3_20);
assertSame(v.toString(), Configuration.getVersionNumber());
try {
@@ -1848,8 +1877,10 @@
assertEquals(3, alg.getDefaultTerminatorLength());
assertNull(alg.getDefaultMTerminator());
assertNull(alg.getDefaultMTerminatorLength());
- assertEquals(DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
- alg.getWordBoundaryMinLength());
+ assertEquals(
+ DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
+ alg.getWordBoundaryMinLength(),
+ 0);
}
{
@@ -1866,8 +1897,10 @@
assertEquals("markupOutput(format=HTML, markup=<span class=trunc>...</span>)",
alg.getDefaultMTerminator().toString());
assertEquals(Integer.valueOf(3), alg.getDefaultMTerminatorLength());
- assertEquals(DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
- alg.getWordBoundaryMinLength());
+ assertEquals(
+ DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
+ alg.getWordBoundaryMinLength(),
+ 0);
}
{
@@ -1888,7 +1921,32 @@
}
}
- @Test
+ public void testCFormat() throws TemplateException {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
+
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, LegacyCFormat.NAME);
+ assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+ cfg.setSetting(Configuration.C_FORMAT_KEY_CAMEL_CASE, JSONCFormat.NAME);
+ assertSame(JSONCFormat.INSTANCE, cfg.getCFormat());
+
+ cfg.setSetting(Configuration.C_FORMAT_KEY_CAMEL_CASE, "default");
+ cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, LegacyCFormat.NAME);
+
+ for (CFormat standardCFormat : new CFormat[] {
+ LegacyCFormat.INSTANCE,
+ JSONCFormat.INSTANCE, JavaScriptCFormat.INSTANCE, JavaScriptOrJSONCFormat.INSTANCE,
+ JavaCFormat.INSTANCE, XSCFormat.INSTANCE
+ }) {
+ cfg.setSetting(Configuration.C_FORMAT_KEY, standardCFormat.getName());
+ assertSame(standardCFormat, cfg.getCFormat());
+ }
+
+ // Object Builder value:
+ cfg.setSetting(Configuration.C_FORMAT_KEY, JSONCFormat.class.getName() + "()");
+ assertSame(JSONCFormat.INSTANCE, cfg.getCFormat());
+ }
+
public void testFallbackOnNullLoopVariable() throws TemplateException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
assertTrue(cfg.getFallbackOnNullLoopVariable());
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 17c6702..541bf1b 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -106,7 +106,7 @@
expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.32
List<Version> actual = new ArrayList<>();
- for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
+ for (int i = _VersionInts.V_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
int major = i / 1000000;
int minor = i % 1000000 / 1000;
int micro = i % 1000;
diff --git a/src/test/java/freemarker/template/utility/StringUtilTest.java b/src/test/java/freemarker/template/utility/StringUtilTest.java
index 557c5dc..1614dc0 100644
--- a/src/test/java/freemarker/template/utility/StringUtilTest.java
+++ b/src/test/java/freemarker/template/utility/StringUtilTest.java
@@ -19,6 +19,8 @@
package freemarker.template.utility;
+import static freemarker.template.utility.StringUtil.JsStringEncCompatibility.*;
+import static freemarker.template.utility.StringUtil.JsStringEncQuotation.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -30,11 +32,12 @@
import org.junit.Test;
import freemarker.core.ParseException;
+import freemarker.template.utility.StringUtil.JsStringEncCompatibility;
public class StringUtilTest {
@Test
- public void testV2319() {
+ public void testJavaScriptStringEncV2319() {
assertEquals("\\n\\r\\f\\b\\t\\x00\\x19", StringUtil.javaScriptStringEnc("\n\r\f\b\t\u0000\u0019"));
}
@@ -449,5 +452,77 @@
StringUtil.RTFEnc(in, sw);
assertEquals(expected, sw.toString());
}
-
+
+ @Test
+ public void jsStringEncQuotationTests() {
+ for (JsStringEncCompatibility anyCompatibility : JsStringEncCompatibility.values()) {
+ JsStringEncCompatibility anyJsonCompatible = anyCompatibility.isJSONCompatible() ? anyCompatibility : JSON;
+
+ assertEquals("", StringUtil.jsStringEnc("", anyCompatibility, null));
+ assertEquals("''", StringUtil.jsStringEnc("", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"\"", StringUtil.jsStringEnc("", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("a", StringUtil.jsStringEnc("a", anyCompatibility, null));
+ assertEquals("a", StringUtil.jsStringEnc("a", anyCompatibility, null));
+ assertEquals("'a'", StringUtil.jsStringEnc("a", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"a\"", StringUtil.jsStringEnc("a", anyCompatibility, QUOTATION_MARK));
+
+ try {
+ StringUtil.jsStringEnc("", anyJsonCompatible, APOSTROPHE);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ assertEquals("a\\'b", StringUtil.jsStringEnc("a'b", JAVA_SCRIPT, null));
+ assertEquals("a'b", StringUtil.jsStringEnc("a'b", JSON, null));
+ assertEquals("a\\u0027b", StringUtil.jsStringEnc("a'b", JAVA_SCRIPT_OR_JSON, null));
+ assertEquals("'a\\'b'", StringUtil.jsStringEnc("a'b", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"a'b\"", StringUtil.jsStringEnc("a'b", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("<\\/e>", StringUtil.jsStringEnc("</e>", anyCompatibility, null));
+ assertEquals("'<\\/e>'", StringUtil.jsStringEnc("</e>", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"<\\/e>\"", StringUtil.jsStringEnc("</e>", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("\\/e>", StringUtil.jsStringEnc("/e>", anyCompatibility, null));
+ assertEquals("'/e>'", StringUtil.jsStringEnc("/e>", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"/e>\"", StringUtil.jsStringEnc("/e>", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("\\>", StringUtil.jsStringEnc(">", JAVA_SCRIPT, null));
+ assertEquals("\\u003E", StringUtil.jsStringEnc(">", JSON, null));
+ assertEquals("'>'", StringUtil.jsStringEnc(">", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\">\"", StringUtil.jsStringEnc(">", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("-\\>", StringUtil.jsStringEnc("->", JAVA_SCRIPT, null));
+ assertEquals("-\\u003E", StringUtil.jsStringEnc("->", JSON, null));
+ assertEquals("'->'", StringUtil.jsStringEnc("->", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"->\"", StringUtil.jsStringEnc("->", JAVA_SCRIPT, QUOTATION_MARK));
+
+ assertEquals("--\\>", StringUtil.jsStringEnc("-->", JAVA_SCRIPT, null));
+ assertEquals("--\\u003E", StringUtil.jsStringEnc("-->", anyJsonCompatible, null));
+ assertEquals("'--\\>'", StringUtil.jsStringEnc("-->", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"--\\>\"", StringUtil.jsStringEnc("-->", JAVA_SCRIPT, QUOTATION_MARK));
+ assertEquals("\"--\\u003E\"", StringUtil.jsStringEnc("-->", anyJsonCompatible, QUOTATION_MARK));
+
+ assertEquals("x->", StringUtil.jsStringEnc("x->", anyCompatibility, null));
+ assertEquals("'x->'", StringUtil.jsStringEnc("x->", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"x->\"", StringUtil.jsStringEnc("x->", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("\\x3C", StringUtil.jsStringEnc("<", JAVA_SCRIPT, null));
+ assertEquals("\\u003C", StringUtil.jsStringEnc("<", JSON, null));
+ assertEquals("'<'", StringUtil.jsStringEnc("<", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"<\"", StringUtil.jsStringEnc("<", anyCompatibility, QUOTATION_MARK));
+
+ assertEquals("\\x3C!", StringUtil.jsStringEnc("<!", JAVA_SCRIPT, null));
+ assertEquals("\\u003C!", StringUtil.jsStringEnc("<!", JSON, null));
+ assertEquals("'\\x3C!'", StringUtil.jsStringEnc("<!", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"\\x3C!\"", StringUtil.jsStringEnc("<!", JAVA_SCRIPT, QUOTATION_MARK));
+ assertEquals("\"\\u003C!\"", StringUtil.jsStringEnc("<!", anyJsonCompatible, QUOTATION_MARK));
+
+ assertEquals("<x", StringUtil.jsStringEnc("<x", anyCompatibility, null));
+ assertEquals("'<x'", StringUtil.jsStringEnc("<x", JAVA_SCRIPT, APOSTROPHE));
+ assertEquals("\"<x\"", StringUtil.jsStringEnc("<x", anyCompatibility, QUOTATION_MARK));
+ }
+ }
+
}
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index f0cb148..5e179a2 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -74,7 +74,7 @@
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.Version;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.NullWriter;
import freemarker.template.utility.StringUtil;
@@ -450,7 +450,7 @@
dataModel.put("dow", dow);
dataModel.put("dowPre22", dow
&& ((DefaultObjectWrapper) conf.getObjectWrapper()).getIncompatibleImprovements()
- .intValue() < _TemplateAPI.VERSION_INT_2_3_22);
+ .intValue() < _VersionInts.V_2_3_22);
}
}
diff --git a/src/test/java/freemarker/test/templatesuite/models/LegacyList.java b/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
index a02199b..e23b60b 100644
--- a/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
+++ b/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
@@ -24,7 +24,7 @@
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
/**
* A little bridge class that subclasses the new SimpleList
@@ -35,7 +35,7 @@
private Iterator iterator;
public LegacyList() {
- super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
}
/**
diff --git a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
index f317b18..5d4049c 100644
--- a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
+++ b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
@@ -24,7 +24,7 @@
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
import freemarker.template.utility.HtmlEscape;
import freemarker.template.utility.StandardCompress;
@@ -34,7 +34,7 @@
public class TransformHashWrapper implements TemplateHashModel,
TemplateScalarModel {
- private SimpleHash m_cHashModel = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+ private SimpleHash m_cHashModel = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
/** Creates new TransformHashWrapper */
public TransformHashWrapper() {
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
index a6f8475..5d8237d 100644
--- a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
+++ b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
@@ -25,9 +25,6 @@
1234567,89
1234567.886
1
-1
-1.000000000000001
-0.0000000000000001
--0.0000000000000001
-1
-0.0000000000000001
+100000.5
+100000.5
+100 000,5
\ No newline at end of file
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/string-builtins1.txt b/src/test/resources/freemarker/test/templatesuite/expected/string-builtins1.txt
index 6e689ac..adeb39a 100644
--- a/src/test/resources/freemarker/test/templatesuite/expected/string-builtins1.txt
+++ b/src/test/resources/freemarker/test/templatesuite/expected/string-builtins1.txt
@@ -110,3 +110,4 @@
[<\/script>] = [<\/script>]
[\u003C![CDATA[] = [\u003C![CDATA[]
[]]\u003E] = []]\u003E]
+
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
index 82a14ed..9d4b61f 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
@@ -28,23 +28,35 @@
${1234567.886}
${1234567.886?c}
${int?c}
-${double?c}
-${double2?c}
-${double3?c}
-${double4?c}
-${bigDecimal?c}
-${bigDecimal2?c}
-<#if iciIntValue gte 2003021>
- <@assertEquals expected="INF" actual="INF"?number?c />
- <@assertEquals expected="INF" actual="INF"?number?c />
- <@assertEquals expected="-INF" actual="-INF"?number?c />
- <@assertEquals expected="-INF" actual="-INF"?number?float?c />
- <@assertEquals expected="NaN" actual="NaN"?number?float?c />
- <@assertEquals expected="NaN" actual="NaN"?number?float?c />
+<#setting number_format = "computer">
+${100000.5}
+<#setting number_format = "c">
+${100000.5}
+<#setting number_format = ",000.##">
+${100000.5}
+<#setting number_format = "c">
+<@assertEquals expected="1" actual=double?string />
+<@assertEquals expected="1.000000000000001" actual=double2?string />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("1E-16", "0.0000000000000001") actual=double3?string />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("-1E-16", "-0.0000000000000001") actual=double4?string />
+<@assertEquals expected="1" actual=bigDecimal?string />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("1E-16", "0.0000000000000001") actual=bigDecimal2?string />
+<#if iciIntValue gte 2003032>
+ <@assertEquals expected="Infinity" actual="INF"?number?string />
+ <@assertEquals expected="Infinity" actual="INF"?number?float?string />
+ <@assertEquals expected="-Infinity" actual="-INF"?number?string />
+ <@assertEquals expected="-Infinity" actual="-INF"?number?float?string />
+ <@assertEquals expected="NaN" actual="NaN"?number?string />
+ <@assertEquals expected="NaN" actual="NaN"?number?float?string />
+<#elseif iciIntValue == 2003031>
+ <@assertEquals expected="INF" actual="INF"?number?string />
+ <@assertEquals expected="INF" actual="INF"?number?float?string />
+ <@assertEquals expected="-INF" actual="-INF"?number?string />
+ <@assertEquals expected="-INF" actual="-INF"?number?float?string />
+ <@assertEquals expected="NaN" actual="NaN"?number?string />
+ <@assertEquals expected="NaN" actual="NaN"?number?float?string />
<#else>
- <#setting locale = "en_US">
- <#setting number_format = "0.#">
- <@assertEquals expected="INF"?number?string actual="INF"?number?c />
- <@assertEquals expected="-INF"?number?string actual="-INF"?number?c />
- <@assertEquals expected="NaN"?number?string actual="NaN"?number?c />
+ <@assertEquals expected="\x221E" actual="INF"?number?string />
+ <@assertEquals expected="-\x221E" actual="-INF"?number?string />
+ <@assertEquals expected="\xFFFD" actual="NaN"?number?string />
</#if>
\ No newline at end of file
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins1.ftl b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins1.ftl
index 84c794f..f2f249e 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins1.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins1.ftl
@@ -127,3 +127,10 @@
[${"</script>"?json_string}] = [<\/script>]
[${"<![CDATA["?json_string}] = [\u003C![CDATA[]
[${"]]>"?json_string}] = []]\u003E]
+
+<#-- ?c_...: -->
+<#setting locale="tr_TR">
+<@assertEquals actual="i"?upper_case expected="\x0130" />
+<@assertEquals actual="i"?c_upper_case expected="I" />
+<@assertEquals actual="I"?lower_case expected="\x0131" />
+<@assertEquals actual="I"?c_lower_case expected="i" />