blob: 48b1aa141b5477558333c1e5e44d1271fdff6b12 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Standards for Technology in Automotive Retail
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Carver - STAR - bug 224197 - initial API and implementation
* based on work from Apache Xalan 2.7.0
*******************************************************************************/
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.
*/
/*
* $Id: NumeratorFormatter.java,v 1.2 2008/03/28 02:38:16 dacarver Exp $
*/
package org.eclipse.wst.xsl.core.internal.compiler.xslt10.transformer;
import java.util.Locale;
import java.util.NoSuchElementException;
import org.w3c.dom.Element;
/**
* Converts enumerated numbers into strings, using the XSL conversion
* attributes. Having this in a class helps avoid being forced to extract the
* attributes repeatedly.
*
* @xsl.usage internal
*/
class NumeratorFormatter {
/** The owning xsl:number element. */
protected Element m_xslNumberElement;
/** An instance of a Tokenizer */
protected NumberFormatStringTokenizer m_formatTokenizer;
/** Locale we need to format in */
protected Locale m_locale;
/** An instance of a NumberFormat */
protected java.text.NumberFormat m_formatter;
/** An instance of a transformer */
protected TransformerImpl m_processor;
/**
* Table to help in converting decimals to roman numerals.
*
* @see org.apache.xalan.transformer.DecimalToRoman
*/
private final static DecimalToRoman m_romanConvertTable[] = {
new DecimalToRoman(1000, "M", 900, "CM"),
new DecimalToRoman(500, "D", 400, "CD"),
new DecimalToRoman(100L, "C", 90L, "XC"),
new DecimalToRoman(50L, "L", 40L, "XL"),
new DecimalToRoman(10L, "X", 9L, "IX"),
new DecimalToRoman(5L, "V", 4L, "IV"),
new DecimalToRoman(1L, "I", 1L, "I") };
/**
* Chars for converting integers into alpha counts.
*
* @see TransformerImpl#int2alphaCount
*/
private final static char[] m_alphaCountTable = {
'Z', // z for zero
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' };
/**
* Construct a NumeratorFormatter using an element that contains XSL number
* conversion attributes - format, letter-value, xml:lang, digit-group-sep,
* n-digits-per-group, and sequence-src.
*
* @param xslNumberElement
* The given xsl:number element
* @param processor
* a non-null transformer instance
*/
public NumeratorFormatter(Element xslNumberElement,
TransformerImpl processor) {
m_xslNumberElement = xslNumberElement;
m_processor = processor;
} // end NumeratorFormatter(Element) constructor
/**
* Convert a long integer into alphabetic counting, in other words count
* using the sequence A B C ... Z AA AB AC.... etc.
*
* @param val
* Value to convert -- must be greater than zero.
* @param table
* a table containing one character for each digit in the radix
* @return String representing alpha count of number.
* @see org.apache.xalan.transformer.DecimalToRoman
*
* Note that the radix of the conversion is inferred from the size of the
* table.
*/
protected String int2alphaCount(int val, char[] table) {
int radix = table.length;
// Create a buffer to hold the result
// TODO: size of the table can be detereined by computing
// logs of the radix. For now, we fake it.
char buf[] = new char[100];
// next character to set in the buffer
int charPos = buf.length - 1; // work backward through buf[]
// index in table of the last character that we stored
int lookupIndex = 1; // start off with anything other than zero to
// make correction work
// Correction number
//
// Correction can take on exactly two values:
//
// 0 if the next character is to be emitted is usual
//
// radix - 1
// if the next char to be emitted should be one less than
// you would expect
//
// For example, consider radix 10, where 1="A" and 10="J"
//
// In this scheme, we count: A, B, C ... H, I, J (not A0 and certainly
// not AJ), A1
//
// So, how do we keep from emitting AJ for 10? After correctly emitting
// the
// J, lookupIndex is zero. We now compute a correction number of 9
// (radix-1).
// In the following line, we'll compute (val+correction) % radix, which
// is,
// (val+9)/10. By this time, val is 1, so we compute (1+9) % 10, which
// is 10 % 10 or zero. So, we'll prepare to emit "JJ", but then we'll
// later suppress the leading J as representing zero (in the mod system,
// it can represent either 10 or zero). In summary, the correction value
// of
// "radix-1" acts like "-1" when run through the mod operator, but with
// the
// desireable characteristic that it never produces a negative number.
int correction = 0;
// TODO: throw error on out of range input
do {
// most of the correction calculation is explained above, the reason
// for the
// term after the "|| " is that it correctly propagates carries
// across
// multiple columns.
correction = ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1)) ? (radix - 1)
: 0;
// index in "table" of the next char to emit
lookupIndex = (val + correction) % radix;
// shift input by one "column"
val = (val / radix);
// if the next value we'd put out would be a leading zero, we're
// done.
if (lookupIndex == 0 && val == 0)
break;
// put out the next character of output
buf[charPos--] = table[lookupIndex];
} while (val > 0);
return new String(buf, charPos + 1, (buf.length - charPos - 1));
}
/**
* Convert a long integer into roman numerals.
*
* @param val
* Value to convert.
* @param prefixesAreOK
* true_ to enable prefix notation (e.g. 4 = "IV"), false_ to
* disable prefix notation (e.g. 4 = "IIII").
* @return Roman numeral string.
* @see DecimalToRoman
* @see m_romanConvertTable
*/
public String long2roman(long val, boolean prefixesAreOK) {
if (val <= 0) {
return "#E(" + val + ")";
}
String roman = "";
int place = 0;
if (val <= 3999L) {
do {
while (val >= m_romanConvertTable[place].m_postValue) {
roman += m_romanConvertTable[place].m_postLetter;
val -= m_romanConvertTable[place].m_postValue;
}
if (prefixesAreOK) {
if (val >= m_romanConvertTable[place].m_preValue) {
roman += m_romanConvertTable[place].m_preLetter;
val -= m_romanConvertTable[place].m_preValue;
}
}
place++;
} while (val > 0);
} else {
roman = "#error";
}
return roman;
} // end long2roman
/**
* This class returns tokens using non-alphanumberic characters as
* delimiters.
*/
class NumberFormatStringTokenizer {
/** Field holding the current position in the string */
private int currentPosition;
/** The total length of the string */
private int maxPosition;
/** The string to tokenize */
private String str;
/**
* Construct a NumberFormatStringTokenizer.
*
* @param str
* The string to tokenize
*/
public NumberFormatStringTokenizer(String str) {
this.str = str;
maxPosition = str.length();
}
/**
* Reset tokenizer so that nextToken() starts from the beginning.
*
*/
public void reset() {
currentPosition = 0;
}
/**
* Returns the next token from this string tokenizer.
*
* @return the next token from this string tokenizer.
* @throws NoSuchElementException
* if there are no more tokens in this tokenizer's string.
*/
public String nextToken() {
if (currentPosition >= maxPosition) {
throw new NoSuchElementException();
}
int start = currentPosition;
while ((currentPosition < maxPosition)
&& Character.isLetterOrDigit(str.charAt(currentPosition))) {
currentPosition++;
}
if ((start == currentPosition)
&& (!Character.isLetterOrDigit(str.charAt(currentPosition)))) {
currentPosition++;
}
return str.substring(start, currentPosition);
}
/**
* Tells if <code>nextToken</code> will throw an exception * if it is
* called.
*
* @return true if <code>nextToken</code> can be called * without
* throwing an exception.
*/
public boolean hasMoreTokens() {
return (currentPosition >= maxPosition) ? false : true;
}
/**
* Calculates the number of times that this tokenizer's
* <code>nextToken</code> method can be called before it generates an
* exception.
*
* @return the number of tokens remaining in the string using the
* current delimiter set.
* @see java.util.StringTokenizer#nextToken()
*/
public int countTokens() {
int count = 0;
int currpos = currentPosition;
while (currpos < maxPosition) {
int start = currpos;
while ((currpos < maxPosition)
&& Character.isLetterOrDigit(str.charAt(currpos))) {
currpos++;
}
if ((start == currpos)
&& (Character.isLetterOrDigit(str.charAt(currpos)) == false)) {
currpos++;
}
count++;
}
return count;
}
} // end NumberFormatStringTokenizer
}