/* * 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.10 2004/02/16 22:54:59 minchau 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 implements Axis { public static final int END = DTM.NULL; protected int _node = END; protected int _nodeType = DOM.FIRST_TYPE - 1; protected int _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 = null; private Vector formatToks = null; private int nSepars = 0; private int nFormats = 0; private static String[] Thousands = {"", "m", "mm", "mmm" }; private static String[] Hundreds = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"}; private static String[] Tens = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"}; private static String[] Ones = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"}; protected NodeCounter(Translet translet, DOM document, DTMAxisIterator iterator) { _translet = translet; _document = document; _iterator = iterator; } /** * Set the start node for this counter. The same NodeCounter * 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(int 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; _format = format; _groupSep = groupSep; _letterValue = letterValue; try { _groupSize = Integer.parseInt(groupSize); } catch (NumberFormatException e) { _groupSize = 0; } final int length = _format.length(); boolean isFirst = true; separFirst = true; separLast = false; separToks = new Vector(); formatToks = new Vector(); /* * 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 node according to the level and * the from and count patterns. */ abstract public String getCounter(); /** * Returns the position of node 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 node 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 node 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; final int length = _format.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; final StringBuffer buffer = new StringBuffer(); // 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, StringBuffer buffer) { char c = format.charAt(0); if (Character.isDigit(c)) { char zero = (char)(c - Character.getNumericValue(c)); StringBuffer temp = buffer; if (_groupSize > 0) { temp = new StringBuffer(); } 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]; } }