com.squarespace.cldrengine.numbers.NumberFormatter Maven / Gradle / Ivy
The newest version!
package com.squarespace.cldrengine.numbers;
import static com.squarespace.cldrengine.utils.StringUtils.firstChar;
import static com.squarespace.cldrengine.utils.StringUtils.isEmpty;
import static com.squarespace.cldrengine.utils.StringUtils.lastChar;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import com.squarespace.cldrengine.api.CurrencySpacingPattern;
import com.squarespace.cldrengine.api.CurrencySpacingPos;
import com.squarespace.cldrengine.api.Decimal;
import com.squarespace.cldrengine.api.NumberSymbolType;
import com.squarespace.cldrengine.decimal.DecimalFormatter;
import com.squarespace.cldrengine.general.GeneralInternals;
import com.squarespace.cldrengine.internal.AbstractValue;
import com.squarespace.cldrengine.parsing.NumberPattern;
import com.squarespace.cldrengine.parsing.WrapperPattern;
public abstract class NumberFormatter implements NumberRenderer {
private final NumberParams params;
public NumberFormatter(NumberParams params) {
this.params = params;
}
public abstract AbstractValue value();
public abstract DecimalFormatter formatter(String decimal, String group);
public R render(Decimal n, NumberPattern pattern, String currencySymbol,
String percentSymbol, String decimalSymbol, int minInt, Boolean grouping,
Integer exponent) {
Map symbols = params.symbols;
boolean currency = !isEmpty(currencySymbol);
String decimal = "";
String currencyDecimal = symbols.get(NumberSymbolType.CURRENCYDECIMAL);
if (!isEmpty(decimalSymbol)) {
decimal = decimalSymbol;
} else if (currency && !isEmpty(currencyDecimal)) {
decimal = currencyDecimal;
} else {
decimal = symbols.get(NumberSymbolType.DECIMAL);
}
String group = "";
String currencyGroup = symbols.get(NumberSymbolType.CURRENCYGROUP);
// Default grouping option to true
grouping = grouping == null ? true : grouping;
if (grouping) {
group = symbols.get(NumberSymbolType.GROUP);
if (!isEmpty(currencyGroup)) {
group = currencyGroup;
}
}
int priGroup = pattern.priGroup;
int secGroup = pattern.secGroup;
if (priGroup <= 0) {
priGroup = this.params.primaryGroupingSize;
}
if (secGroup <= 0) {
secGroup = this.params.secondaryGroupingSize;
}
DecimalFormatter formatter = this.formatter(decimal, group);
n.format(
formatter,
decimal,
group,
minInt,
this.params.minimumGroupingDigits,
priGroup,
secGroup,
true, // zeroScale
this.params.digits);
R formatted = formatter.render();
AbstractValue res = this.value();
boolean haveNumber = false;
boolean currencyBefore = false;
int currencyIdx = -1;
for (Object node : pattern.nodes) {
if (node instanceof String) {
res.add("literal", (String) node);
} else {
NumberPattern.Field field = (NumberPattern.Field) node;
switch (field) {
case CURRENCY: {
// Save the offset to the segment before or after the currency symbol
currencyBefore = !haveNumber;
int i = res.length();
res.add("currency", currencySymbol);
int j = res.length();
currencyIdx = currencyBefore ? j : i - 1;
break;
}
case MINUS:
res.add("minus", symbols.get(NumberSymbolType.MINUSSIGN));
break;
case PLUS:
res.add("plus", symbols.get(NumberSymbolType.PLUSSIGN));
break;
case NUMBER:
res.append(formatted);
haveNumber = true;
break;
case PERCENT:
res.add("percent", percentSymbol);
break;
case EXPONENT:
// Don't emit the exponent if undefined or zero
if (exponent != null && exponent != 0) {
res.add("exponent", symbols.get(NumberSymbolType.EXPONENTIAL));
if (exponent < 0) {
res.add("minus", symbols.get(NumberSymbolType.MINUSSIGN));
} else {
res.add("plus", symbols.get(NumberSymbolType.PLUSSIGN));
}
String exp = DecimalNumberingSystem.fastFormatDecimal(exponent.toString(), this.params.digits, 1);
res.add("integer", exp);
break;
}
}
}
}
// Adjust spacing between currency symbol based on surrounding characters.
if (currencyIdx != -1) {
Map> spacing = this.params.currencySpacing;
String curr = res.get(currencyIdx);
if (currencyBefore) {
Map pos = spacing.get(CurrencySpacingPos.AFTER);
if (insertBetween(pos, lastChar(currencySymbol), firstChar(curr))) {
res.insert(currencyIdx, "spacer", pos.get(CurrencySpacingPattern.INSERTBETWEEN));
}
} else {
Map pos = spacing.get(CurrencySpacingPos.BEFORE);
if (insertBetween(pos, firstChar(currencySymbol), lastChar(curr))) {
res.insert(currencyIdx + 1, "spacer", pos.get(CurrencySpacingPattern.INSERTBETWEEN));
}
}
}
return res.render();
}
private static final Pattern RE_SYMBOL = Pattern.compile("^[\\p{Sm}\\p{Sc}\\p{Sk}\\p{So}\\p{Zs}\\p{Zl}\\p{Zp}]");
private static final Pattern RE_DIGIT = Pattern.compile("^[\\p{Nd}]");
protected boolean insertBetween(Map spacing, String currency, String surrounding) {
String currencyMatch = spacing.get(CurrencySpacingPattern.CURRENCYMATCH);
String surroundingMatch = spacing.get(CurrencySpacingPattern.SURROUNDINGMATCH);
return testMatch(currencyMatch, currency) && testMatch(surroundingMatch, surrounding);
}
protected boolean testMatch(String spacing, String value) {
switch (spacing) {
case "[:digit:]":
return RE_DIGIT.matcher(value).matches();
case "[[:^S:]&[:^Z:]]":
return !RE_SYMBOL.matcher(value).matches();
}
return false;
}
public R empty() {
return this.value().render();
}
public R make(String type, String value) {
AbstractValue v = this.value();
v.add(type, value);
return v.render();
}
public R wrap(GeneralInternals internal, String raw, List args) {
AbstractValue res = this.value();
WrapperPattern pattern = internal.parseWrapper(raw);
for (Object node : pattern.nodes) {
if (node instanceof String) {
res.add("literal", (String)node);
} else {
int n = (int)node;
if (n < args.size()) {
R v = args.get(n);
if (v != null) {
res.append(v);
}
}
}
}
return res.render();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy