blob: cad02f51c414aa1f7d1fcd925223936699d16513 [file] [log] [blame]
/*
* reserved comment block
* DO NOT REMOVE OR ALTER!
*/
/*
* Copyright 2001-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: NodeCounter.java,v 1.2.4.1 2005/09/12 11:52:36 pvedula Exp $
*/
package com.sun.org.apache.xalan.internal.xsltc.dom;
import java.util.Vector;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.Translet;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
/**
* @author Jacek Ambroziak
* @author Santiago Pericas-Geertsen
* @author Morten Jorgensen
*/
public abstract class NodeCounter {
public static final int END = DTM.NULL;
protected int _node = END;
protected int _nodeType = DOM.FIRST_TYPE - 1;
protected double _value = Integer.MIN_VALUE;
public final DOM _document;
public final DTMAxisIterator _iterator;
public final Translet _translet;
protected String _format;
protected String _lang;
protected String _letterValue;
protected String _groupSep;
protected int _groupSize;
private boolean _separFirst = true;
private boolean _separLast = false;
private Vector _separToks = new Vector();
private Vector _formatToks = new Vector();
private int _nSepars = 0;
private int _nFormats = 0;
private final static String[] Thousands =
{"", "m", "mm", "mmm" };
private final static String[] Hundreds =
{"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
private final static String[] Tens =
{"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
private final static String[] Ones =
{"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
private StringBuilder _tempBuffer = new StringBuilder();
/**
* Indicates if this instance of xsl:number has a from pattern.
*/
protected boolean _hasFrom;
protected NodeCounter(Translet translet,
DOM document, DTMAxisIterator iterator) {
_translet = translet;
_document = document;
_iterator = iterator;
}
protected NodeCounter(Translet translet,
DOM document, DTMAxisIterator iterator, boolean hasFrom) {
_translet = translet;
_document = document;
_iterator = iterator;
_hasFrom = hasFrom;
}
/**
* Set the start node for this counter. The same <tt>NodeCounter</tt>
* object can be used multiple times by resetting the starting node.
*/
abstract public NodeCounter setStartNode(int node);
/**
* If the user specified a value attribute, use this instead of
* counting nodes.
*/
public NodeCounter setValue(double value) {
_value = value;
return this;
}
/**
* Sets formatting fields before calling formatNumbers().
*/
protected void setFormatting(String format, String lang, String letterValue,
String groupSep, String groupSize) {
_lang = lang;
_groupSep = groupSep;
_letterValue = letterValue;
_groupSize = parseStringToAnInt(groupSize);
setTokens(format);
}
/**
* Effectively does the same thing as Integer.parseInt(String s) except
* instead of throwing a NumberFormatException, it returns 0. This method
* is used instead of Integer.parseInt() since it does not incur the
* overhead of throwing an Exception which is expensive.
*
* @param s A String to be parsed into an int.
* @return Either an int represented by the incoming String s, or 0 if
* the parsing is not successful.
*/
private int parseStringToAnInt(String s) {
if (s == null)
return 0;
int result = 0;
boolean negative = false;
int radix = 10, i = 0, max = s.length();
int limit, multmin, digit;
if (max > 0) {
if (s.charAt(0) == '-') {
negative = true;
limit = Integer.MIN_VALUE;
i++;
} else {
limit = -Integer.MAX_VALUE;
}
multmin = limit / radix;
if (i < max) {
digit = Character.digit(s.charAt(i++), radix);
if (digit < 0)
return 0;
else
result = -digit;
}
while (i < max) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++), radix);
if (digit < 0)
return 0;
if (result < multmin)
return 0;
result *= radix;
if (result < limit + digit)
return 0;
result -= digit;
}
} else {
return 0;
}
if (negative) {
if (i > 1)
return result;
else /* Only got "-" */
return 0;
} else {
return -result;
}
}
// format == null assumed here
private final void setTokens(final String format){
if( (_format!=null) &&(format.equals(_format)) ){// has already been set
return;
}
_format = format;
// reset
final int length = _format.length();
boolean isFirst = true;
_separFirst = true;
_separLast = false;
_nSepars = 0;
_nFormats = 0;
_separToks.clear() ;
_formatToks.clear();
/*
* Tokenize the format string into alphanumeric and non-alphanumeric
* tokens as described in M. Kay page 241.
*/
for (int j = 0, i = 0; i < length;) {
char c = format.charAt(i);
for (j = i; Character.isLetterOrDigit(c);) {
if (++i == length) break;
c = format.charAt(i);
}
if (i > j) {
if (isFirst) {
_separToks.addElement(".");
isFirst = _separFirst = false;
}
_formatToks.addElement(format.substring(j, i));
}
if (i == length) break;
c = format.charAt(i);
for (j = i; !Character.isLetterOrDigit(c);) {
if (++i == length) break;
c = format.charAt(i);
isFirst = false;
}
if (i > j) {
_separToks.addElement(format.substring(j, i));
}
}
_nSepars = _separToks.size();
_nFormats = _formatToks.size();
if (_nSepars > _nFormats) _separLast = true;
if (_separFirst) _nSepars--;
if (_separLast) _nSepars--;
if (_nSepars == 0) {
_separToks.insertElementAt(".", 1);
_nSepars++;
}
if (_separFirst) _nSepars ++;
}
/**
* Sets formatting fields to their default values.
*/
public NodeCounter setDefaultFormatting() {
setFormatting("1", "en", "alphabetic", null, null);
return this;
}
/**
* Returns the position of <tt>node</tt> according to the level and
* the from and count patterns.
*/
abstract public String getCounter();
/**
* Returns the position of <tt>node</tt> according to the level and
* the from and count patterns. This position is converted into a
* string based on the arguments passed.
*/
public String getCounter(String format, String lang, String letterValue,
String groupSep, String groupSize) {
setFormatting(format, lang, letterValue, groupSep, groupSize);
return getCounter();
}
/**
* Returns true if <tt>node</tt> matches the count pattern. By
* default a node matches the count patterns if it is of the
* same type as the starting node.
*/
public boolean matchesCount(int node) {
return _nodeType == _document.getExpandedTypeID(node);
}
/**
* Returns true if <tt>node</tt> matches the from pattern. By default,
* no node matches the from pattern.
*/
public boolean matchesFrom(int node) {
return false;
}
/**
* Format a single value according to the format parameters.
*/
protected String formatNumbers(int value) {
return formatNumbers(new int[] { value });
}
/**
* Format a sequence of values according to the format paramaters
* set by calling setFormatting().
*/
protected String formatNumbers(int[] values) {
final int nValues = values.length;
boolean isEmpty = true;
for (int i = 0; i < nValues; i++)
if (values[i] != Integer.MIN_VALUE)
isEmpty = false;
if (isEmpty) return("");
// Format the output string using the values array and the fmt. tokens
boolean isFirst = true;
int t = 0, n = 0, s = 1;
_tempBuffer.setLength(0);
final StringBuilder buffer = _tempBuffer;
// Append separation token before first digit/letter/numeral
if (_separFirst) buffer.append((String)_separToks.elementAt(0));
// Append next digit/letter/numeral and separation token
while (n < nValues) {
final int value = values[n];
if (value != Integer.MIN_VALUE) {
if (!isFirst) buffer.append((String) _separToks.elementAt(s++));
formatValue(value, (String)_formatToks.elementAt(t++), buffer);
if (t == _nFormats) t--;
if (s >= _nSepars) s--;
isFirst = false;
}
n++;
}
// Append separation token after last digit/letter/numeral
if (_separLast) buffer.append((String)_separToks.lastElement());
return buffer.toString();
}
/**
* Format a single value based on the appropriate formatting token.
* This method is based on saxon (Michael Kay) and only implements
* lang="en".
*/
private void formatValue(int value, String format, StringBuilder buffer) {
char c = format.charAt(0);
if (Character.isDigit(c)) {
char zero = (char)(c - Character.getNumericValue(c));
StringBuilder temp = buffer;
if (_groupSize > 0) {
temp = new StringBuilder();
}
String s = "";
int n = value;
while (n > 0) {
s = (char) ((int) zero + (n % 10)) + s;
n = n / 10;
}
for (int i = 0; i < format.length() - s.length(); i++) {
temp.append(zero);
}
temp.append(s);
if (_groupSize > 0) {
for (int i = 0; i < temp.length(); i++) {
if (i != 0 && ((temp.length() - i) % _groupSize) == 0) {
buffer.append(_groupSep);
}
buffer.append(temp.charAt(i));
}
}
}
else if (c == 'i' && !_letterValue.equals("alphabetic")) {
buffer.append(romanValue(value));
}
else if (c == 'I' && !_letterValue.equals("alphabetic")) {
buffer.append(romanValue(value).toUpperCase());
}
else {
int min = (int) c;
int max = (int) c;
// Special case for Greek alphabet
if (c >= 0x3b1 && c <= 0x3c9) {
max = 0x3c9; // omega
}
else {
// General case: search for end of group
while (Character.isLetterOrDigit((char) (max + 1))) {
max++;
}
}
buffer.append(alphaValue(value, min, max));
}
}
private String alphaValue(int value, int min, int max) {
if (value <= 0) {
return "" + value;
}
int range = max - min + 1;
char last = (char)(((value-1) % range) + min);
if (value > range) {
return alphaValue((value-1) / range, min, max) + last;
}
else {
return "" + last;
}
}
private String romanValue(int n) {
if (n <= 0 || n > 4000) {
return "" + n;
}
return
Thousands[n / 1000] +
Hundreds[(n / 100) % 10] +
Tens[(n/10) % 10] +
Ones[n % 10];
}
}