1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: NodeCounter.java,v 1.10 2004/02/16 22:54:59 minchau Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.xsltc.dom;
  20. import java.util.Vector;
  21. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  22. import com.sun.org.apache.xalan.internal.xsltc.Translet;
  23. import com.sun.org.apache.xml.internal.dtm.DTM;
  24. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  25. /**
  26. * @author Jacek Ambroziak
  27. * @author Santiago Pericas-Geertsen
  28. * @author Morten Jorgensen
  29. */
  30. public abstract class NodeCounter implements Axis {
  31. public static final int END = DTM.NULL;
  32. protected int _node = END;
  33. protected int _nodeType = DOM.FIRST_TYPE - 1;
  34. protected int _value = Integer.MIN_VALUE;
  35. public final DOM _document;
  36. public final DTMAxisIterator _iterator;
  37. public final Translet _translet;
  38. protected String _format;
  39. protected String _lang;
  40. protected String _letterValue;
  41. protected String _groupSep;
  42. protected int _groupSize;
  43. private boolean separFirst = true;
  44. private boolean separLast = false;
  45. private Vector separToks = null;
  46. private Vector formatToks = null;
  47. private int nSepars = 0;
  48. private int nFormats = 0;
  49. private static String[] Thousands =
  50. {"", "m", "mm", "mmm" };
  51. private static String[] Hundreds =
  52. {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
  53. private static String[] Tens =
  54. {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
  55. private static String[] Ones =
  56. {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
  57. protected NodeCounter(Translet translet,
  58. DOM document, DTMAxisIterator iterator) {
  59. _translet = translet;
  60. _document = document;
  61. _iterator = iterator;
  62. }
  63. /**
  64. * Set the start node for this counter. The same <tt>NodeCounter</tt>
  65. * object can be used multiple times by resetting the starting node.
  66. */
  67. abstract public NodeCounter setStartNode(int node);
  68. /**
  69. * If the user specified a value attribute, use this instead of
  70. * counting nodes.
  71. */
  72. public NodeCounter setValue(int value) {
  73. _value = value;
  74. return this;
  75. }
  76. /**
  77. * Sets formatting fields before calling formatNumbers().
  78. */
  79. protected void setFormatting(String format, String lang, String letterValue,
  80. String groupSep, String groupSize) {
  81. _lang = lang;
  82. _format = format;
  83. _groupSep = groupSep;
  84. _letterValue = letterValue;
  85. try {
  86. _groupSize = Integer.parseInt(groupSize);
  87. }
  88. catch (NumberFormatException e) {
  89. _groupSize = 0;
  90. }
  91. final int length = _format.length();
  92. boolean isFirst = true;
  93. separFirst = true;
  94. separLast = false;
  95. separToks = new Vector();
  96. formatToks = new Vector();
  97. /*
  98. * Tokenize the format string into alphanumeric and non-alphanumeric
  99. * tokens as described in M. Kay page 241.
  100. */
  101. for (int j = 0, i = 0; i < length;) {
  102. char c = _format.charAt(i);
  103. for (j = i; Character.isLetterOrDigit(c);) {
  104. if (++i == length) break;
  105. c = _format.charAt(i);
  106. }
  107. if (i > j) {
  108. if (isFirst) {
  109. separToks.addElement(".");
  110. isFirst = separFirst = false;
  111. }
  112. formatToks.addElement(_format.substring(j, i));
  113. }
  114. if (i == length) break;
  115. c = _format.charAt(i);
  116. for (j = i; !Character.isLetterOrDigit(c);) {
  117. if (++i == length) break;
  118. c = _format.charAt(i);
  119. isFirst = false;
  120. }
  121. if (i > j) {
  122. separToks.addElement(_format.substring(j, i));
  123. }
  124. }
  125. nSepars = separToks.size();
  126. nFormats = formatToks.size();
  127. if (nSepars > nFormats) separLast = true;
  128. if (separFirst) nSepars--;
  129. if (separLast) nSepars--;
  130. if (nSepars == 0) {
  131. separToks.insertElementAt(".", 1);
  132. nSepars++;
  133. }
  134. if (separFirst) nSepars ++;
  135. }
  136. /**
  137. * Sets formatting fields to their default values.
  138. */
  139. public NodeCounter setDefaultFormatting() {
  140. setFormatting("1", "en", "alphabetic", null, null);
  141. return this;
  142. }
  143. /**
  144. * Returns the position of <tt>node</tt> according to the level and
  145. * the from and count patterns.
  146. */
  147. abstract public String getCounter();
  148. /**
  149. * Returns the position of <tt>node</tt> according to the level and
  150. * the from and count patterns. This position is converted into a
  151. * string based on the arguments passed.
  152. */
  153. public String getCounter(String format, String lang, String letterValue,
  154. String groupSep, String groupSize) {
  155. setFormatting(format, lang, letterValue, groupSep, groupSize);
  156. return getCounter();
  157. }
  158. /**
  159. * Returns true if <tt>node</tt> matches the count pattern. By
  160. * default a node matches the count patterns if it is of the
  161. * same type as the starting node.
  162. */
  163. public boolean matchesCount(int node) {
  164. return _nodeType == _document.getExpandedTypeID(node);
  165. }
  166. /**
  167. * Returns true if <tt>node</tt> matches the from pattern. By default,
  168. * no node matches the from pattern.
  169. */
  170. public boolean matchesFrom(int node) {
  171. return false;
  172. }
  173. /**
  174. * Format a single value according to the format parameters.
  175. */
  176. protected String formatNumbers(int value) {
  177. return formatNumbers(new int[] { value });
  178. }
  179. /**
  180. * Format a sequence of values according to the format paramaters
  181. * set by calling setFormatting().
  182. */
  183. protected String formatNumbers(int[] values) {
  184. final int nValues = values.length;
  185. final int length = _format.length();
  186. boolean isEmpty = true;
  187. for (int i = 0; i < nValues; i++)
  188. if (values[i] != Integer.MIN_VALUE)
  189. isEmpty = false;
  190. if (isEmpty) return("");
  191. // Format the output string using the values array and the fmt. tokens
  192. boolean isFirst = true;
  193. int t = 0, n = 0, s = 1;
  194. final StringBuffer buffer = new StringBuffer();
  195. // Append separation token before first digit/letter/numeral
  196. if (separFirst) buffer.append((String)separToks.elementAt(0));
  197. // Append next digit/letter/numeral and separation token
  198. while (n < nValues) {
  199. final int value = values[n];
  200. if (value != Integer.MIN_VALUE) {
  201. if (!isFirst) buffer.append((String) separToks.elementAt(s++));
  202. formatValue(value, (String)formatToks.elementAt(t++), buffer);
  203. if (t == nFormats) t--;
  204. if (s >= nSepars) s--;
  205. isFirst = false;
  206. }
  207. n++;
  208. }
  209. // Append separation token after last digit/letter/numeral
  210. if (separLast) buffer.append((String)separToks.lastElement());
  211. return buffer.toString();
  212. }
  213. /**
  214. * Format a single value based on the appropriate formatting token.
  215. * This method is based on saxon (Michael Kay) and only implements
  216. * lang="en".
  217. */
  218. private void formatValue(int value, String format, StringBuffer buffer) {
  219. char c = format.charAt(0);
  220. if (Character.isDigit(c)) {
  221. char zero = (char)(c - Character.getNumericValue(c));
  222. StringBuffer temp = buffer;
  223. if (_groupSize > 0) {
  224. temp = new StringBuffer();
  225. }
  226. String s = "";
  227. int n = value;
  228. while (n > 0) {
  229. s = (char) ((int) zero + (n % 10)) + s;
  230. n = n / 10;
  231. }
  232. for (int i = 0; i < format.length() - s.length(); i++) {
  233. temp.append(zero);
  234. }
  235. temp.append(s);
  236. if (_groupSize > 0) {
  237. for (int i = 0; i < temp.length(); i++) {
  238. if (i != 0 && ((temp.length() - i) % _groupSize) == 0) {
  239. buffer.append(_groupSep);
  240. }
  241. buffer.append(temp.charAt(i));
  242. }
  243. }
  244. }
  245. else if (c == 'i' && !_letterValue.equals("alphabetic")) {
  246. buffer.append(romanValue(value));
  247. }
  248. else if (c == 'I' && !_letterValue.equals("alphabetic")) {
  249. buffer.append(romanValue(value).toUpperCase());
  250. }
  251. else {
  252. int min = (int) c;
  253. int max = (int) c;
  254. // Special case for Greek alphabet
  255. if (c >= 0x3b1 && c <= 0x3c9) {
  256. max = 0x3c9; // omega
  257. }
  258. else {
  259. // General case: search for end of group
  260. while (Character.isLetterOrDigit((char) (max + 1))) {
  261. max++;
  262. }
  263. }
  264. buffer.append(alphaValue(value, min, max));
  265. }
  266. }
  267. private String alphaValue(int value, int min, int max) {
  268. if (value <= 0) {
  269. return "" + value;
  270. }
  271. int range = max - min + 1;
  272. char last = (char)(((value-1) % range) + min);
  273. if (value > range) {
  274. return alphaValue((value-1) / range, min, max) + last;
  275. }
  276. else {
  277. return "" + last;
  278. }
  279. }
  280. private String romanValue(int n) {
  281. if (n <= 0 || n > 4000) {
  282. return "" + n;
  283. }
  284. return
  285. Thousands[n / 1000] +
  286. Hundreds[(n / 100) % 10] +
  287. Tens[(n10) % 10] +
  288. Ones[n % 10];
  289. }
  290. }