/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.xsl;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntArray;
import java.util.ArrayList;
/**
* Formatting for the xsl:number action.
*/
public class XslNumberFormat {
private String head;
private String tail;
private String []separators;
private int []formats;
private int []zeroSize;
private String format;
private String lang;
private boolean isAlphabetic;
private String groupSeparator;
private int groupSize;
/**
* Create a new number formatting object.
*
* @param format format string as specified by the XSLT draft
* @param lang language for alphanumeric numbering
* @param isAlphabetic resolves ambiguity for alphanumeric numbering
* @param groupSeparator separator between number grouping
* @param groupSize digits between group separator
*/
public XslNumberFormat(String format, String lang, boolean isAlphabetic,
String groupSeparator, int groupSize)
{
this.format = format;
this.lang = lang;
this.isAlphabetic = isAlphabetic;
if (groupSize <= 0) {
groupSize = 3;
groupSeparator = "";
}
if (groupSeparator == null)
groupSeparator = "";
this.groupSeparator = groupSeparator;
this.groupSize = groupSize;
if (format == null)
format = "1";
int headIndex = format.length();
ArrayList separators = new ArrayList();
IntArray zeroSizes = new IntArray();
IntArray formats = new IntArray();
CharBuffer cb = new CharBuffer();
int i = 0;
while (i < format.length()) {
char ch;
// scan the separator
cb.clear();
for (; i < format.length(); i++) {
ch = format.charAt(i);
if (Character.isLetterOrDigit(ch))
break;
cb.append(ch);
}
// head and tail separators are just sticked on the ends
if (head == null)
head = cb.toString();
else if (i >= format.length())
tail = cb.toString();
else
separators.add(cb.toString());
if (i >= format.length())
break;
// scan the format code
int zeroSize = 1;
int code = '0';
for (; i < format.length(); i++) {
ch = format.charAt(i);
if (! Character.isLetterOrDigit(ch))
break;
if (! Character.isDigit(ch)) {
if (code != '0' || zeroSize != 1)
code = 0;
else
code = ch;
}
else if (Character.digit(ch, 10) == 0 && zeroSize >= 0)
zeroSize++;
else if (Character.digit(ch, 10) == 1)
code = ch - 1;
else
code = 0;
}
if (code == 0)
code = '0';
zeroSizes.add(zeroSize);
formats.add(code);
}
// default format is '1'
if (formats.size() == 0) {
tail = head;
head = "";
formats.add('0');
zeroSizes.add(0);
}
// default separator is '.'
if (separators.size() == 0)
separators.add(".");
if (separators.size() < formats.size())
separators.add(separators.get(separators.size() - 1));
this.separators = (String []) separators.toArray(new String[separators.size()]);
this.zeroSize = zeroSizes.toArray();
this.formats = formats.toArray();
if (head == null)
head = "";
if (tail == null)
tail = "";
}
public String getFormat()
{
return format;
}
public String getLang()
{
return lang;
}
public boolean isAlphabetic()
{
return isAlphabetic;
}
public String getGroupSeparator()
{
return groupSeparator;
}
public int getGroupSize()
{
return groupSize;
}
/**
* Converts the array of numbers into a formatted number
*
* @param numbers array of numbers
*/
void format(XslWriter out, IntArray numbers)
{
CharBuffer buf = new CharBuffer();
buf.append(head);
int i;
for (i = numbers.size() - 1; i >= 0; i--) {
int index = numbers.size() - i - 1;
if (index >= formats.length)
index = formats.length - 1;
char code = (char) formats[index];
int zeroCount = zeroSize[index];
int count = numbers.get(i);
switch (code) {
case 'i':
romanize(buf, "mdclxvi", count);
break;
case 'I':
romanize(buf, "MDCLXVI", count);
break;
case 'a':
formatAlpha(buf, 'a', count);
break;
case 'A':
formatAlpha(buf, 'A', count);
break;
default:
formatDecimal(buf, code, zeroCount, count);
break;
}
if (i > 0)
buf.append(separators[index]);
}
buf.append(tail);
out.print(buf.toString());
}
/**
* Formats a roman numeral. Numbers bigger than 5000 are formatted as
* a decimal.
*
* @param cb buffer to accumulate the result
* @param xvi roman characters, e.g. "mdclxvi" and "MDCLXVI"
* @param count the number to convert,
*/
private void romanize(CharBuffer cb, String xvi, int count)
{
if (count <= 0)
throw new RuntimeException();
if (count > 5000) {
cb.append(count);
return;
}
for (; count > 1000; count -= 1000)
cb.append(xvi.charAt(0));
romanize(cb, xvi.charAt(0), xvi.charAt(1), xvi.charAt(2), count / 100);
count %= 100;
romanize(cb, xvi.charAt(2), xvi.charAt(3), xvi.charAt(4), count / 10);
count %= 10;
romanize(cb, xvi.charAt(4), xvi.charAt(5), xvi.charAt(6), count);
}
/**
* Convert a single decimal digit to a roman number
*
* @param cb buffer to accumulate the result.
* @param x character for the tens number
* @param v character for the fives number
* @param i character for the ones number
* @param count digit to convert
*/
private void romanize(CharBuffer cb, char x, char v, char i, int count)
{
switch (count) {
case 0:
break;
case 1:
cb.append(i);
break;
case 2:
cb.append(i);
cb.append(i);
break;
case 3:
cb.append(i);
cb.append(i);
cb.append(i);
break;
case 4:
cb.append(i);
cb.append(v);
break;
case 5:
cb.append(v);
break;
case 6:
cb.append(v);
cb.append(i);
break;
case 7:
cb.append(v);
cb.append(i);
cb.append(i);
break;
case 8:
cb.append(v);
cb.append(i);
cb.append(i);
cb.append(i);
break;
case 9:
cb.append(i);
cb.append(x);
break;
default:
throw new RuntimeException();
}
}
/**
* Format an alphabetic number. Only English encodings are supported.
*
* @param cb buffer to accumulate results
* @param a starting character
* @param count number to convert
*/
private void formatAlpha(CharBuffer cb, char a, int count)
{
if (count <= 0)
throw new RuntimeException();
int index = cb.length();
while (count > 0) {
count--;
cb.insert(index, (char) (a + count % 26));
count /= 26;
}
}
/**
* Format a decimal number.
*
* @param cb buffer to accumulate results
* @param code code for the zero digit
* @param zeroCount minimum digits
* @param count number to convert
*/
private void formatDecimal(CharBuffer cb, int code,
int zeroCount, int count)
{
int digits = 0;
int index = cb.length();
while (count > 0) {
if (digits > 0 && digits % groupSize == 0)
cb.insert(index, groupSeparator);
cb.insert(index, (char) (code + count % 10));
count /= 10;
digits++;
}
while (cb.length() - index < zeroCount) {
cb.insert(index, (char) code);
}
}
}