package client.net.sf.saxon.ce.expr.number;
import client.net.sf.saxon.ce.lib.Numberer;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.tree.util.UTF16CharacterSet;
import client.net.sf.saxon.ce.value.IntegerValue;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* Class NumberFormatter defines a method to format a ArrayList of integers as a character
* string according to a supplied format specification.
* @author Michael H. Kay
*/
public class NumberFormatter{
private ArrayList formatTokens;
private ArrayList punctuationTokens;
private boolean startsWithPunctuation;
/**
* Tokenize the format pattern.
* @param format the format specification. Contains one of the following values:<ul>
* <li>"1": conventional decimal numbering</li>
* <li>"a": sequence a, b, c, ... aa, ab, ac, ...</li>
* <li>"A": sequence A, B, C, ... AA, AB, AC, ...</li>
* <li>"i": sequence i, ii, iii, iv, v ...</li>
* <li>"I": sequence I, II, III, IV, V, ...</li>
* </ul>
* This symbol may be preceded and followed by punctuation (any other characters) which is
* copied to the output string.
*/
public void prepare(String format) {
// Tokenize the format string into alternating alphanumeric and non-alphanumeric tokens
if (format.length()==0) {
format="1";
}
formatTokens = new ArrayList(10);
punctuationTokens = new ArrayList(10);
int len = format.length();
int i=0;
int t;
boolean first = true;
startsWithPunctuation = true;
while (i<len) {
int c = format.charAt(i);
t=i;
if (UTF16CharacterSet.isHighSurrogate(c)) {
c = UTF16CharacterSet.combinePair((char)c, format.charAt(++i));
}
while (isLetterOrDigit(c)) {
i++;
if (i==len) break;
c = format.charAt(i);
if (UTF16CharacterSet.isHighSurrogate(c)) {
c = UTF16CharacterSet.combinePair((char)c, format.charAt(++i));
}
}
if (i>t) {
String tok = format.substring(t, i);
formatTokens.add(tok);
if (first) {
punctuationTokens.add(".");
startsWithPunctuation = false;
first = false;
}
}
if (i==len) break;
t=i;
c = format.charAt(i);
if (UTF16CharacterSet.isHighSurrogate(c)) {
c = UTF16CharacterSet.combinePair((char)c, format.charAt(++i));
}
while (!isLetterOrDigit(c)) {
first = false;
i++;
if (i==len) break;
c = format.charAt(i);
if (UTF16CharacterSet.isHighSurrogate(c)) {
c = UTF16CharacterSet.combinePair((char)c, format.charAt(++i));
}
}
if (i>t) {
String sep = format.substring(t, i);
punctuationTokens.add(sep);
}
}
if (formatTokens.isEmpty()) {
formatTokens.add("1");
if (punctuationTokens.size() == 1) {
punctuationTokens.add(punctuationTokens.get(0));
}
}
}
/**
* Determine whether a (possibly non-BMP) character is a letter or digit.
* @param c the codepoint of the character to be tested
* @return true if this is a number or letter as defined in the XSLT rules for xsl:number pictures.
*/
private static boolean isLetterOrDigit(int c) {
if (c <= 0x7F) {
// Fast path for ASCII characters
return (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
} else {
return Alphanumeric.isAlphanumeric(c);
}
}
/**
* Format a list of numbers.
* @param numbers the numbers to be formatted (a sequence of integer values; it may also contain
* preformatted strings as part of the error recovery fallback)
* @return the formatted output string.
*/
public CharSequence format(List numbers, int groupSize, String groupSeparator,
String letterValue, String ordinal, Numberer numberer) {
FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.TINY);
int num = 0;
int tok = 0;
// output first punctuation token
if (startsWithPunctuation) {
sb.append((String)punctuationTokens.get(tok));
}
// output the list of numbers
while (num<numbers.size()) {
if (num>0) {
if (tok==0 && startsWithPunctuation) {
// The first punctuation token isn't a separator if it appears before the first
// formatting token. Such a punctuation token is used only once, at the start.
sb.append(".");
} else {
sb.append((String)punctuationTokens.get(tok));
}
}
Object o = numbers.get(num++);
String s;
if (o instanceof Long) {
long nr = ((Long)o).longValue();
RegularGroupFormatter rgf = new RegularGroupFormatter(groupSize, groupSeparator);
s = numberer.format(nr, (String)formatTokens.get(tok), rgf, letterValue, ordinal);
} else if (o instanceof BigDecimal) {
s = new IntegerValue((BigDecimal)o).getStringValue();
} else {
s = o.toString();
}
sb.append(s);
tok++;
if (tok==formatTokens.size()) tok--;
}
// output the final punctuation token
if (punctuationTokens.size()>formatTokens.size()) {
sb.append((String)punctuationTokens.get(punctuationTokens.size()-1));
}
return sb.condense();
}
}
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.