001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: NodeCounter.java 468651 2006-10-28 07:04:25Z minchau $
020 */
021
022 package org.apache.xalan.xsltc.dom;
023
024 import java.util.Vector;
025
026 import org.apache.xalan.xsltc.DOM;
027 import org.apache.xalan.xsltc.Translet;
028 import org.apache.xml.dtm.DTM;
029 import org.apache.xml.dtm.DTMAxisIterator;
030 import org.apache.xml.dtm.Axis;
031
032 /**
033 * @author Jacek Ambroziak
034 * @author Santiago Pericas-Geertsen
035 * @author Morten Jorgensen
036 */
037 public abstract class NodeCounter {
038 public static final int END = DTM.NULL;
039
040 protected int _node = END;
041 protected int _nodeType = DOM.FIRST_TYPE - 1;
042 protected double _value = Integer.MIN_VALUE;
043
044 public final DOM _document;
045 public final DTMAxisIterator _iterator;
046 public final Translet _translet;
047
048 protected String _format;
049 protected String _lang;
050 protected String _letterValue;
051 protected String _groupSep;
052 protected int _groupSize;
053
054 private boolean _separFirst = true;
055 private boolean _separLast = false;
056 private Vector _separToks = new Vector();
057 private Vector _formatToks = new Vector();
058 private int _nSepars = 0;
059 private int _nFormats = 0;
060
061 private final static String[] Thousands =
062 {"", "m", "mm", "mmm" };
063 private final static String[] Hundreds =
064 {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
065 private final static String[] Tens =
066 {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
067 private final static String[] Ones =
068 {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
069
070 private StringBuffer _tempBuffer = new StringBuffer();
071
072 protected NodeCounter(Translet translet,
073 DOM document, DTMAxisIterator iterator) {
074 _translet = translet;
075 _document = document;
076 _iterator = iterator;
077 }
078
079 /**
080 * Set the start node for this counter. The same <tt>NodeCounter</tt>
081 * object can be used multiple times by resetting the starting node.
082 */
083 abstract public NodeCounter setStartNode(int node);
084
085 /**
086 * If the user specified a value attribute, use this instead of
087 * counting nodes.
088 */
089 public NodeCounter setValue(double value) {
090 _value = value;
091 return this;
092 }
093
094 /**
095 * Sets formatting fields before calling formatNumbers().
096 */
097 protected void setFormatting(String format, String lang, String letterValue,
098 String groupSep, String groupSize) {
099 _lang = lang;
100 _groupSep = groupSep;
101 _letterValue = letterValue;
102
103 try {
104 _groupSize = Integer.parseInt(groupSize);
105 }
106 catch (NumberFormatException e) {
107 _groupSize = 0;
108 }
109 setTokens(format);
110
111 }
112
113 // format == null assumed here
114 private final void setTokens(final String format){
115 if( (_format!=null) &&(format.equals(_format)) ){// has already been set
116 return;
117 }
118 _format = format;
119 // reset
120 final int length = _format.length();
121 boolean isFirst = true;
122 _separFirst = true;
123 _separLast = false;
124 _nSepars = 0;
125 _nFormats = 0;
126 _separToks.clear() ;
127 _formatToks.clear();
128
129 /*
130 * Tokenize the format string into alphanumeric and non-alphanumeric
131 * tokens as described in M. Kay page 241.
132 */
133 for (int j = 0, i = 0; i < length;) {
134 char c = format.charAt(i);
135 for (j = i; Character.isLetterOrDigit(c);) {
136 if (++i == length) break;
137 c = format.charAt(i);
138 }
139 if (i > j) {
140 if (isFirst) {
141 _separToks.addElement(".");
142 isFirst = _separFirst = false;
143 }
144 _formatToks.addElement(format.substring(j, i));
145 }
146
147 if (i == length) break;
148
149 c = format.charAt(i);
150 for (j = i; !Character.isLetterOrDigit(c);) {
151 if (++i == length) break;
152 c = format.charAt(i);
153 isFirst = false;
154 }
155 if (i > j) {
156 _separToks.addElement(format.substring(j, i));
157 }
158 }
159
160 _nSepars = _separToks.size();
161 _nFormats = _formatToks.size();
162 if (_nSepars > _nFormats) _separLast = true;
163
164 if (_separFirst) _nSepars--;
165 if (_separLast) _nSepars--;
166 if (_nSepars == 0) {
167 _separToks.insertElementAt(".", 1);
168 _nSepars++;
169 }
170 if (_separFirst) _nSepars ++;
171
172 }
173 /**
174 * Sets formatting fields to their default values.
175 */
176 public NodeCounter setDefaultFormatting() {
177 setFormatting("1", "en", "alphabetic", null, null);
178 return this;
179 }
180
181 /**
182 * Returns the position of <tt>node</tt> according to the level and
183 * the from and count patterns.
184 */
185 abstract public String getCounter();
186
187 /**
188 * Returns the position of <tt>node</tt> according to the level and
189 * the from and count patterns. This position is converted into a
190 * string based on the arguments passed.
191 */
192 public String getCounter(String format, String lang, String letterValue,
193 String groupSep, String groupSize) {
194 setFormatting(format, lang, letterValue, groupSep, groupSize);
195 return getCounter();
196 }
197
198 /**
199 * Returns true if <tt>node</tt> matches the count pattern. By
200 * default a node matches the count patterns if it is of the
201 * same type as the starting node.
202 */
203 public boolean matchesCount(int node) {
204 return _nodeType == _document.getExpandedTypeID(node);
205 }
206
207 /**
208 * Returns true if <tt>node</tt> matches the from pattern. By default,
209 * no node matches the from pattern.
210 */
211 public boolean matchesFrom(int node) {
212 return false;
213 }
214
215 /**
216 * Format a single value according to the format parameters.
217 */
218 protected String formatNumbers(int value) {
219 return formatNumbers(new int[] { value });
220 }
221
222 /**
223 * Format a sequence of values according to the format paramaters
224 * set by calling setFormatting().
225 */
226 protected String formatNumbers(int[] values) {
227 final int nValues = values.length;
228 final int length = _format.length();
229
230 boolean isEmpty = true;
231 for (int i = 0; i < nValues; i++)
232 if (values[i] != Integer.MIN_VALUE)
233 isEmpty = false;
234 if (isEmpty) return("");
235
236 // Format the output string using the values array and the fmt. tokens
237 boolean isFirst = true;
238 int t = 0, n = 0, s = 1;
239 _tempBuffer.setLength(0);
240 final StringBuffer buffer = _tempBuffer;
241
242 // Append separation token before first digit/letter/numeral
243 if (_separFirst) buffer.append((String)_separToks.elementAt(0));
244
245 // Append next digit/letter/numeral and separation token
246 while (n < nValues) {
247 final int value = values[n];
248 if (value != Integer.MIN_VALUE) {
249 if (!isFirst) buffer.append((String) _separToks.elementAt(s++));
250 formatValue(value, (String)_formatToks.elementAt(t++), buffer);
251 if (t == _nFormats) t--;
252 if (s >= _nSepars) s--;
253 isFirst = false;
254 }
255 n++;
256 }
257
258 // Append separation token after last digit/letter/numeral
259 if (_separLast) buffer.append((String)_separToks.lastElement());
260 return buffer.toString();
261 }
262
263 /**
264 * Format a single value based on the appropriate formatting token.
265 * This method is based on saxon (Michael Kay) and only implements
266 * lang="en".
267 */
268 private void formatValue(int value, String format, StringBuffer buffer) {
269 char c = format.charAt(0);
270
271 if (Character.isDigit(c)) {
272 char zero = (char)(c - Character.getNumericValue(c));
273
274 StringBuffer temp = buffer;
275 if (_groupSize > 0) {
276 temp = new StringBuffer();
277 }
278 String s = "";
279 int n = value;
280 while (n > 0) {
281 s = (char) ((int) zero + (n % 10)) + s;
282 n = n / 10;
283 }
284
285 for (int i = 0; i < format.length() - s.length(); i++) {
286 temp.append(zero);
287 }
288 temp.append(s);
289
290 if (_groupSize > 0) {
291 for (int i = 0; i < temp.length(); i++) {
292 if (i != 0 && ((temp.length() - i) % _groupSize) == 0) {
293 buffer.append(_groupSep);
294 }
295 buffer.append(temp.charAt(i));
296 }
297 }
298 }
299 else if (c == 'i' && !_letterValue.equals("alphabetic")) {
300 buffer.append(romanValue(value));
301 }
302 else if (c == 'I' && !_letterValue.equals("alphabetic")) {
303 buffer.append(romanValue(value).toUpperCase());
304 }
305 else {
306 int min = (int) c;
307 int max = (int) c;
308
309 // Special case for Greek alphabet
310 if (c >= 0x3b1 && c <= 0x3c9) {
311 max = 0x3c9; // omega
312 }
313 else {
314 // General case: search for end of group
315 while (Character.isLetterOrDigit((char) (max + 1))) {
316 max++;
317 }
318 }
319 buffer.append(alphaValue(value, min, max));
320 }
321 }
322
323 private String alphaValue(int value, int min, int max) {
324 if (value <= 0) {
325 return "" + value;
326 }
327
328 int range = max - min + 1;
329 char last = (char)(((value-1) % range) + min);
330 if (value > range) {
331 return alphaValue((value-1) / range, min, max) + last;
332 }
333 else {
334 return "" + last;
335 }
336 }
337
338 private String romanValue(int n) {
339 if (n <= 0 || n > 4000) {
340 return "" + n;
341 }
342 return
343 Thousands[n / 1000] +
344 Hundreds[(n / 100) % 10] +
345 Tens[(n/10) % 10] +
346 Ones[n % 10];
347 }
348
349 }
350