blob: ccaca18700e2a67d50010f778e8ec2fed5106324 [file] [log] [blame]
/*
* 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 java.util.Locale;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.utility.StringUtil;
/**
* Formats like {@code %G} in {@code printf}, with the specified number of significant digits. Also has special
* formatter for HTML output format, where it uses the HTML "sup" element for exponents.
*/
public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
public static final PrintfGTemplateNumberFormatFactory INSTANCE = new PrintfGTemplateNumberFormatFactory();
private PrintfGTemplateNumberFormatFactory() {
// Defined to decrease visibility
}
@Override
public TemplateNumberFormat get(String params, Locale locale, Environment env)
throws InvalidFormatParametersException {
Integer significantDigits;
if (!params.isEmpty()) {
try {
significantDigits = Integer.valueOf(params);
} catch (NumberFormatException e) {
throw new InvalidFormatParametersException(
"The format parameter must be an integer, but was (shown quoted) "
+ StringUtil.jQuote(params) + ".");
}
} else {
// Use the default of %G
significantDigits = null;
}
return new PrintfGTemplateNumberFormat(significantDigits, locale);
}
private static class PrintfGTemplateNumberFormat extends TemplateNumberFormat {
private final Locale locale;
private final String printfFormat;
private PrintfGTemplateNumberFormat(Integer significantDigits, Locale locale) {
this.printfFormat = "%" + (significantDigits != null ? "." + significantDigits : "") + "G";
this.locale = locale;
}
@Override
public String formatToPlainText(TemplateNumberModel numberModel)
throws UnformattableValueException, TemplateModelException {
final Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
// printf %G only accepts Double, BigDecimal and Float
final Number gCompatibleN;
if (n instanceof Double || n instanceof BigDecimal || n instanceof Float) {
gCompatibleN = n;
} else {
if (n instanceof BigInteger) {
gCompatibleN = new BigDecimal((BigInteger) n);
} else if (n instanceof Long) {
gCompatibleN = BigDecimal.valueOf(((Long) n).longValue());
} else {
gCompatibleN = Double.valueOf(n.doubleValue());
}
}
return String.format(locale, printfFormat, gCompatibleN);
}
@Override
public Object format(TemplateNumberModel numberModel)
throws UnformattableValueException, TemplateModelException {
String strResult = formatToPlainText(numberModel);
int expIdx = strResult.indexOf('E');
if (expIdx == -1) {
return strResult;
}
String expStr = strResult.substring(expIdx + 1);
int expSignifNumBegin = 0;
while (expSignifNumBegin < expStr.length() && isExpSignifNumPrefix(expStr.charAt(expSignifNumBegin))) {
expSignifNumBegin++;
}
return HTMLOutputFormat.INSTANCE.fromMarkup(
strResult.substring(0, expIdx)
+ "*10<sup>"
+ (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expSignifNumBegin)
+ "</sup>");
}
private boolean isExpSignifNumPrefix(char c) {
return c == '+' || c == '-' || c == '0';
}
@Override
public boolean isLocaleBound() {
return true;
}
@Override
public String getDescription() {
return "printf " + printfFormat;
}
}
}