http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java new file mode 100644 index 0000000..0d56137 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateMethodModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.SimpleScalar; +import org.apache.freemarker.core.model.impl.SimpleSequence; +import org.apache.freemarker.core.util._StringUtil; + + +/** + * Contains the string built-ins that correspond to basic regular expressions operations. + */ +class BuiltInsForStringsRegexp { + + static class groupsBI extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) throws TemplateException { + TemplateModel targetModel = target.eval(env); + assertNonNull(targetModel, env); + if (targetModel instanceof RegexMatchModel) { + return ((RegexMatchModel) targetModel).getGroups(); + } else if (targetModel instanceof RegexMatchModel.MatchWithGroups) { + return ((RegexMatchModel.MatchWithGroups) targetModel).groupsSeq; + } else { + throw new UnexpectedTypeException(target, targetModel, + "regular expression matcher", + new Class[] { RegexMatchModel.class, RegexMatchModel.MatchWithGroups.class }, + env); + } + } + } + + static class matchesBI extends BuiltInForString { + class MatcherBuilder implements TemplateMethodModel { + + String matchString; + + MatcherBuilder(String matchString) throws TemplateModelException { + this.matchString = matchString; + } + + @Override + public Object exec(List args) throws TemplateModelException { + int argCnt = args.size(); + checkMethodArgCount(argCnt, 1, 2); + + String patternString = (String) args.get(0); + long flags = argCnt > 1 ? RegexpHelper.parseFlagString((String) args.get(1)) : 0; + if ((flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0) { + RegexpHelper.logFlagWarning("?" + key + " doesn't support the \"f\" flag."); + } + Pattern pattern = RegexpHelper.getPattern(patternString, (int) flags); + return new RegexMatchModel(pattern, matchString); + } + } + + @Override + TemplateModel calculateResult(String s, Environment env) throws TemplateModelException { + return new MatcherBuilder(s); + } + + } + + static class replace_reBI extends BuiltInForString { + + class ReplaceMethod implements TemplateMethodModel { + private String s; + + ReplaceMethod(String s) { + this.s = s; + } + + @Override + public Object exec(List args) throws TemplateModelException { + int argCnt = args.size(); + checkMethodArgCount(argCnt, 2, 3); + String arg1 = (String) args.get(0); + String arg2 = (String) args.get(1); + long flags = argCnt > 2 ? RegexpHelper.parseFlagString((String) args.get(2)) : 0; + String result; + if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) { + RegexpHelper.checkNonRegexpFlags("replace", flags); + result = _StringUtil.replace(s, arg1, arg2, + (flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) != 0, + (flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0); + } else { + Pattern pattern = RegexpHelper.getPattern(arg1, (int) flags); + Matcher matcher = pattern.matcher(s); + result = (flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0 + ? matcher.replaceFirst(arg2) + : matcher.replaceAll(arg2); + } + return new SimpleScalar(result); + } + + } + + @Override + TemplateModel calculateResult(String s, Environment env) throws TemplateModelException { + return new ReplaceMethod(s); + } + + } + + // Represents the match + + static class RegexMatchModel + implements TemplateBooleanModel, TemplateCollectionModel, TemplateSequenceModel { + static class MatchWithGroups implements TemplateScalarModel { + final String matchedInputPart; + final SimpleSequence groupsSeq; + + MatchWithGroups(String input, Matcher matcher) { + matchedInputPart = input.substring(matcher.start(), matcher.end()); + final int grpCount = matcher.groupCount() + 1; + groupsSeq = new SimpleSequence(grpCount); + for (int i = 0; i < grpCount; i++) { + groupsSeq.add(matcher.group(i)); + } + } + + @Override + public String getAsString() { + return matchedInputPart; + } + } + final Pattern pattern; + + final String input; + private Matcher firedEntireInputMatcher; + private Boolean entireInputMatched; + + private TemplateSequenceModel entireInputMatchGroups; + + private ArrayList matchingInputParts; + + RegexMatchModel(Pattern pattern, String input) { + this.pattern = pattern; + this.input = input; + } + + @Override + public TemplateModel get(int i) throws TemplateModelException { + ArrayList matchingInputParts = this.matchingInputParts; + if (matchingInputParts == null) { + matchingInputParts = getMatchingInputPartsAndStoreResults(); + } + return (TemplateModel) matchingInputParts.get(i); + } + + @Override + public boolean getAsBoolean() { + Boolean result = entireInputMatched; + return result != null ? result.booleanValue() : isEntrieInputMatchesAndStoreResults(); + } + + TemplateModel getGroups() { + TemplateSequenceModel entireInputMatchGroups = this.entireInputMatchGroups; + if (entireInputMatchGroups == null) { + Matcher t = firedEntireInputMatcher; + if (t == null) { + isEntrieInputMatchesAndStoreResults(); + t = firedEntireInputMatcher; + } + final Matcher firedEntireInputMatcher = t; + + entireInputMatchGroups = new TemplateSequenceModel() { + + @Override + public TemplateModel get(int i) throws TemplateModelException { + try { + return new SimpleScalar(firedEntireInputMatcher.group(i)); + } catch (Exception e) { + throw new _TemplateModelException(e, "Failed to read match group"); + } + } + + @Override + public int size() throws TemplateModelException { + try { + return firedEntireInputMatcher.groupCount() + 1; + } catch (Exception e) { + throw new _TemplateModelException(e, "Failed to get match group count"); + } + } + + }; + this.entireInputMatchGroups = entireInputMatchGroups; + } + return entireInputMatchGroups; + } + + private ArrayList getMatchingInputPartsAndStoreResults() throws TemplateModelException { + ArrayList matchingInputParts = new ArrayList(); + + Matcher matcher = pattern.matcher(input); + while (matcher.find()) { + matchingInputParts.add(new MatchWithGroups(input, matcher)); + } + + this.matchingInputParts = matchingInputParts; + return matchingInputParts; + } + + private boolean isEntrieInputMatchesAndStoreResults() { + Matcher matcher = pattern.matcher(input); + boolean matches = matcher.matches(); + firedEntireInputMatcher = matcher; + entireInputMatched = Boolean.valueOf(matches); + return matches; + } + + @Override + public TemplateModelIterator iterator() { + final ArrayList matchingInputParts = this.matchingInputParts; + if (matchingInputParts == null) { + final Matcher matcher = pattern.matcher(input); + return new TemplateModelIterator() { + + private int nextIdx = 0; + boolean hasFindInfo = matcher.find(); + + @Override + public boolean hasNext() { + final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts; + if (matchingInputParts == null) { + return hasFindInfo; + } else { + return nextIdx < matchingInputParts.size(); + } + } + + @Override + public TemplateModel next() throws TemplateModelException { + final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts; + if (matchingInputParts == null) { + if (!hasFindInfo) throw new _TemplateModelException("There were no more matches"); + MatchWithGroups result = new MatchWithGroups(input, matcher); + nextIdx++; + hasFindInfo = matcher.find(); + return result; + } else { + try { + return (TemplateModel) matchingInputParts.get(nextIdx++); + } catch (IndexOutOfBoundsException e) { + throw new _TemplateModelException(e, "There were no more matches"); + } + } + } + + }; + } else { + return new TemplateModelIterator() { + + private int nextIdx = 0; + + @Override + public boolean hasNext() { + return nextIdx < matchingInputParts.size(); + } + + @Override + public TemplateModel next() throws TemplateModelException { + try { + return (TemplateModel) matchingInputParts.get(nextIdx++); + } catch (IndexOutOfBoundsException e) { + throw new _TemplateModelException(e, "There were no more matches"); + } + } + }; + } + } + + @Override + public int size() throws TemplateModelException { + ArrayList matchingInputParts = this.matchingInputParts; + if (matchingInputParts == null) { + matchingInputParts = getMatchingInputPartsAndStoreResults(); + } + return matchingInputParts.size(); + } + } + + // Can't be instantiated + private BuiltInsForStringsRegexp() { } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java new file mode 100644 index 0000000..a3b5689 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.freemarker.core.Token; +import org.apache.freemarker.core.model.TemplateModel; + + +final class BuiltInsWithParseTimeParameters { + + /** + * Behaves similarly to the ternary operator of Java. + */ + static class then_BI extends BuiltInWithParseTimeParameters { + + private ASTExpression whenTrueExp; + private ASTExpression whenFalseExp; + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + boolean lho = target.evalToBoolean(env); + return (lho ? whenTrueExp : whenFalseExp).evalToNonMissing(env); + } + + @Override + void bindToParameters(List parameters, Token openParen, Token closeParen) throws ParseException { + if (parameters.size() != 2) { + throw newArgumentCountException("requires exactly 2", openParen, closeParen); + } + whenTrueExp = (ASTExpression) parameters.get(0); + whenFalseExp = (ASTExpression) parameters.get(1); + } + + @Override + protected ASTExpression getArgumentParameterValue(final int argIdx) { + switch (argIdx) { + case 0: return whenTrueExp; + case 1: return whenFalseExp; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + protected int getArgumentsCount() { + return 2; + } + + @Override + protected List getArgumentsAsList() { + ArrayList args = new ArrayList(2); + args.add(whenTrueExp); + args.add(whenFalseExp); + return args; + } + + @Override + protected void cloneArguments(ASTExpression cloneExp, String replacedIdentifier, + ASTExpression replacement, ReplacemenetState replacementState) { + then_BI clone = (then_BI) cloneExp; + clone.whenTrueExp = whenTrueExp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState); + clone.whenFalseExp = whenFalseExp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState); + } + + } + + private BuiltInsWithParseTimeParameters() { + // Not to be instantiated + } + + static class switch_BI extends BuiltInWithParseTimeParameters { + + private List/*<ASTExpression>*/ parameters; + + @Override + void bindToParameters(List parameters, Token openParen, Token closeParen) throws ParseException { + if (parameters.size() < 2) { + throw newArgumentCountException("must have at least 2", openParen, closeParen); + } + this.parameters = parameters; + } + + @Override + protected List getArgumentsAsList() { + return parameters; + } + + @Override + protected int getArgumentsCount() { + return parameters.size(); + } + + @Override + protected ASTExpression getArgumentParameterValue(int argIdx) { + return (ASTExpression) parameters.get(argIdx); + } + + @Override + protected void cloneArguments(ASTExpression clone, String replacedIdentifier, ASTExpression replacement, + ReplacemenetState replacementState) { + ArrayList parametersClone = new ArrayList(parameters.size()); + for (int i = 0; i < parameters.size(); i++) { + parametersClone.add(((ASTExpression) parameters.get(i)) + .deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState)); + } + ((switch_BI) clone).parameters = parametersClone; + } + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + TemplateModel targetValue = target.evalToNonMissing(env); + + List parameters = this.parameters; + int paramCnt = parameters.size(); + for (int i = 0; i + 1 < paramCnt; i += 2) { + ASTExpression caseExp = (ASTExpression) parameters.get(i); + TemplateModel caseValue = caseExp.evalToNonMissing(env); + if (EvalUtil.compare( + targetValue, target, + EvalUtil.CMP_OP_EQUALS, "==", + caseValue, caseExp, + this, true, + false, false, false, + env)) { + return ((ASTExpression) parameters.get(i + 1)).evalToNonMissing(env); + } + } + + if (paramCnt % 2 == 0) { + throw new _MiscTemplateException(target, + "The value before ?", key, "(case1, value1, case2, value2, ...) didn't match any of the " + + "case parameters, and there was no default value parameter (an additional last parameter) " + + "eithter. "); + } + return ((ASTExpression) parameters.get(paramCnt - 1)).evalToNonMissing(env); + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java new file mode 100644 index 0000000..69a2465 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +/** + * Represents the CSS output format (MIME type "text/css", name "CSS"). This format doesn't support escaping. + * + * @since 2.3.24 + */ +public class CSSOutputFormat extends OutputFormat { + + /** + * The only instance (singleton) of this {@link OutputFormat}. + */ + public static final CSSOutputFormat INSTANCE = new CSSOutputFormat(); + + private CSSOutputFormat() { + // Only to decrease visibility + } + + @Override + public String getName() { + return "CSS"; + } + + @Override + public String getMimeType() { + return "text/css"; + } + + @Override + public boolean isOutputFormatMixingAllowed() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java new file mode 100644 index 0000000..0aebeab --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +/** + * Thrown by {@link DirectiveCallPlace#getOrCreateCustomData(Object, org.apache.freemarker.core.util.ObjectFactory)} + * + * @since 2.3.22 + */ +public class CallPlaceCustomDataInitializationException extends Exception { + + public CallPlaceCustomDataInitializationException(String message, Throwable cause) { + super(message, cause); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java new file mode 100644 index 0000000..d629547 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Represents two markup formats nested into each other. For example, markdown nested into HTML. + * + * @since 2.3.24 + */ +public final class CombinedMarkupOutputFormat extends CommonMarkupOutputFormat<TemplateCombinedMarkupOutputModel> { + + private final String name; + + private final MarkupOutputFormat outer; + private final MarkupOutputFormat inner; + + /** + * Same as {@link #CombinedMarkupOutputFormat(String, MarkupOutputFormat, MarkupOutputFormat)} with {@code null} as + * the {@code name} parameter. + */ + public CombinedMarkupOutputFormat(MarkupOutputFormat outer, MarkupOutputFormat inner) { + this(null, outer, inner); + } + + /** + * @param name + * Maybe {@code null}, in which case it defaults to + * <code>outer.getName() + "{" + inner.getName() + "}"</code>. + */ + public CombinedMarkupOutputFormat(String name, MarkupOutputFormat outer, MarkupOutputFormat inner) { + this.name = name != null ? null : outer.getName() + "{" + inner.getName() + "}"; + this.outer = outer; + this.inner = inner; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getMimeType() { + return outer.getMimeType(); + } + + @Override + public void output(String textToEsc, Writer out) throws IOException, TemplateModelException { + outer.output(inner.escapePlainText(textToEsc), out); + } + + @Override + public String escapePlainText(String plainTextContent) throws TemplateModelException { + return outer.escapePlainText(inner.escapePlainText(plainTextContent)); + } + + @Override + public boolean isLegacyBuiltInBypassed(String builtInName) throws TemplateModelException { + return outer.isLegacyBuiltInBypassed(builtInName); + } + + @Override + public boolean isAutoEscapedByDefault() { + return outer.isAutoEscapedByDefault(); + } + + @Override + public boolean isOutputFormatMixingAllowed() { + return outer.isOutputFormatMixingAllowed(); + } + + public MarkupOutputFormat getOuterOutputFormat() { + return outer; + } + + public MarkupOutputFormat getInnerOutputFormat() { + return inner; + } + + @Override + protected TemplateCombinedMarkupOutputModel newTemplateMarkupOutputModel( + String plainTextContent, String markupContent) { + return new TemplateCombinedMarkupOutputModel(plainTextContent, markupContent, this); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java new file mode 100644 index 0000000..cde0163 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Common superclass for implementing {@link MarkupOutputFormat}-s that use a {@link CommonTemplateMarkupOutputModel} + * subclass. + * + * @since 2.3.24 + */ +public abstract class CommonMarkupOutputFormat<MO extends CommonTemplateMarkupOutputModel> + extends MarkupOutputFormat<MO> { + + protected CommonMarkupOutputFormat() { + // Only to decrease visibility + } + + @Override + public final MO fromPlainTextByEscaping(String textToEsc) throws TemplateModelException { + return newTemplateMarkupOutputModel(textToEsc, null); + } + + @Override + public final MO fromMarkup(String markupText) throws TemplateModelException { + return newTemplateMarkupOutputModel(null, markupText); + } + + @Override + public final void output(MO mo, Writer out) throws IOException, TemplateModelException { + String mc = mo.getMarkupContent(); + if (mc != null) { + out.write(mc); + } else { + output(mo.getPlainTextContent(), out); + } + } + + @Override + public abstract void output(String textToEsc, Writer out) throws IOException, TemplateModelException; + + @Override + public final String getSourcePlainText(MO mo) throws TemplateModelException { + return mo.getPlainTextContent(); + } + + @Override + public final String getMarkupString(MO mo) throws TemplateModelException { + String mc = mo.getMarkupContent(); + if (mc != null) { + return mc; + } + + mc = escapePlainText(mo.getPlainTextContent()); + mo.setMarkupContent(mc); + return mc; + } + + @Override + public final MO concat(MO mo1, MO mo2) throws TemplateModelException { + String pc1 = mo1.getPlainTextContent(); + String mc1 = mo1.getMarkupContent(); + String pc2 = mo2.getPlainTextContent(); + String mc2 = mo2.getMarkupContent(); + + String pc3 = pc1 != null && pc2 != null ? pc1 + pc2 : null; + String mc3 = mc1 != null && mc2 != null ? mc1 + mc2 : null; + if (pc3 != null || mc3 != null) { + return newTemplateMarkupOutputModel(pc3, mc3); + } + + if (pc1 != null) { + return newTemplateMarkupOutputModel(null, getMarkupString(mo1) + mc2); + } else { + return newTemplateMarkupOutputModel(null, mc1 + getMarkupString(mo2)); + } + } + + @Override + public boolean isEmpty(MO mo) throws TemplateModelException { + String s = mo.getPlainTextContent(); + if (s != null) { + return s.length() == 0; + } + return mo.getMarkupContent().length() == 0; + } + + @Override + public boolean isOutputFormatMixingAllowed() { + return false; + } + + @Override + public boolean isAutoEscapedByDefault() { + return true; + } + + /** + * Creates a new {@link CommonTemplateMarkupOutputModel} that's bound to this {@link OutputFormat} instance. + */ + protected abstract MO newTemplateMarkupOutputModel(String plainTextContent, String markupContent) + throws TemplateModelException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java new file mode 100644 index 0000000..809c248 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +/** + * Common superclass for implementing {@link TemplateMarkupOutputModel}-s that belong to a + * {@link CommonMarkupOutputFormat} subclass format. + * + * <p> + * Thread-safe after proper publishing. Calculated fields (typically, the markup calculated from plain text) might will + * be re-calculated for multiple times if accessed from multiple threads (this only affects performance, not + * functionality). + * + * @since 2.3.24 + */ +public abstract class CommonTemplateMarkupOutputModel<MO extends CommonTemplateMarkupOutputModel<MO>> + implements TemplateMarkupOutputModel<MO> { + + private final String plainTextContent; + private String markupContent; + + /** + * A least one of the parameters must be non-{@code null}! + */ + protected CommonTemplateMarkupOutputModel(String plainTextContent, String markupContent) { + this.plainTextContent = plainTextContent; + this.markupContent = markupContent; + } + + @Override + public abstract CommonMarkupOutputFormat<MO> getOutputFormat(); + + /** Maybe {@code null}, but then {@link #getMarkupContent()} isn't {@code null}. */ + final String getPlainTextContent() { + return plainTextContent; + } + + /** Maybe {@code null}, but then {@link #getPlainTextContent()} isn't {@code null}. */ + final String getMarkupContent() { + return markupContent; + } + + /** + * Use only to set the value calculated from {@link #getPlainTextContent()}, when {@link #getMarkupContent()} was + * still {@code null}! + */ + final void setMarkupContent(String markupContent) { + this.markupContent = markupContent; + } + +}
