001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.xsl;
030:
031: import com.caucho.util.CharBuffer;
032: import com.caucho.util.IntArray;
033:
034: import java.util.ArrayList;
035:
036: /**
037: * Formatting for the xsl:number action.
038: */
039: public class XslNumberFormat {
040: private String head;
041: private String tail;
042:
043: private String[] separators;
044: private int[] formats;
045: private int[] zeroSize;
046:
047: private String format;
048: private String lang;
049: private boolean isAlphabetic;
050: private String groupSeparator;
051: private int groupSize;
052:
053: /**
054: * Create a new number formatting object.
055: *
056: * @param format format string as specified by the XSLT draft
057: * @param lang language for alphanumeric numbering
058: * @param isAlphabetic resolves ambiguity for alphanumeric numbering
059: * @param groupSeparator separator between number grouping
060: * @param groupSize digits between group separator
061: */
062: public XslNumberFormat(String format, String lang,
063: boolean isAlphabetic, String groupSeparator, int groupSize) {
064: this .format = format;
065: this .lang = lang;
066: this .isAlphabetic = isAlphabetic;
067: if (groupSize <= 0) {
068: groupSize = 3;
069: groupSeparator = "";
070: }
071: if (groupSeparator == null)
072: groupSeparator = "";
073: this .groupSeparator = groupSeparator;
074: this .groupSize = groupSize;
075:
076: if (format == null)
077: format = "1";
078:
079: int headIndex = format.length();
080:
081: ArrayList separators = new ArrayList();
082: IntArray zeroSizes = new IntArray();
083: IntArray formats = new IntArray();
084:
085: CharBuffer cb = new CharBuffer();
086: int i = 0;
087: while (i < format.length()) {
088: char ch;
089:
090: // scan the separator
091: cb.clear();
092: for (; i < format.length(); i++) {
093: ch = format.charAt(i);
094: if (Character.isLetterOrDigit(ch))
095: break;
096: cb.append(ch);
097: }
098:
099: // head and tail separators are just sticked on the ends
100: if (head == null)
101: head = cb.toString();
102: else if (i >= format.length())
103: tail = cb.toString();
104: else
105: separators.add(cb.toString());
106:
107: if (i >= format.length())
108: break;
109:
110: // scan the format code
111: int zeroSize = 1;
112: int code = '0';
113: for (; i < format.length(); i++) {
114: ch = format.charAt(i);
115: if (!Character.isLetterOrDigit(ch))
116: break;
117:
118: if (!Character.isDigit(ch)) {
119: if (code != '0' || zeroSize != 1)
120: code = 0;
121: else
122: code = ch;
123: } else if (Character.digit(ch, 10) == 0
124: && zeroSize >= 0)
125: zeroSize++;
126: else if (Character.digit(ch, 10) == 1)
127: code = ch - 1;
128: else
129: code = 0;
130: }
131: if (code == 0)
132: code = '0';
133:
134: zeroSizes.add(zeroSize);
135: formats.add(code);
136: }
137:
138: // default format is '1'
139: if (formats.size() == 0) {
140: tail = head;
141: head = "";
142: formats.add('0');
143: zeroSizes.add(0);
144: }
145:
146: // default separator is '.'
147: if (separators.size() == 0)
148: separators.add(".");
149: if (separators.size() < formats.size())
150: separators.add(separators.get(separators.size() - 1));
151:
152: this .separators = (String[]) separators
153: .toArray(new String[separators.size()]);
154: this .zeroSize = zeroSizes.toArray();
155: this .formats = formats.toArray();
156:
157: if (head == null)
158: head = "";
159: if (tail == null)
160: tail = "";
161: }
162:
163: public String getFormat() {
164: return format;
165: }
166:
167: public String getLang() {
168: return lang;
169: }
170:
171: public boolean isAlphabetic() {
172: return isAlphabetic;
173: }
174:
175: public String getGroupSeparator() {
176: return groupSeparator;
177: }
178:
179: public int getGroupSize() {
180: return groupSize;
181: }
182:
183: /**
184: * Converts the array of numbers into a formatted number
185: *
186: * @param numbers array of numbers
187: */
188: void format(XslWriter out, IntArray numbers) {
189: CharBuffer buf = new CharBuffer();
190:
191: buf.append(head);
192:
193: int i;
194: for (i = numbers.size() - 1; i >= 0; i--) {
195: int index = numbers.size() - i - 1;
196: if (index >= formats.length)
197: index = formats.length - 1;
198:
199: char code = (char) formats[index];
200: int zeroCount = zeroSize[index];
201:
202: int count = numbers.get(i);
203:
204: switch (code) {
205: case 'i':
206: romanize(buf, "mdclxvi", count);
207: break;
208:
209: case 'I':
210: romanize(buf, "MDCLXVI", count);
211: break;
212:
213: case 'a':
214: formatAlpha(buf, 'a', count);
215: break;
216:
217: case 'A':
218: formatAlpha(buf, 'A', count);
219: break;
220:
221: default:
222: formatDecimal(buf, code, zeroCount, count);
223: break;
224: }
225:
226: if (i > 0)
227: buf.append(separators[index]);
228: }
229:
230: buf.append(tail);
231:
232: out.print(buf.toString());
233: }
234:
235: /**
236: * Formats a roman numeral. Numbers bigger than 5000 are formatted as
237: * a decimal.
238: *
239: * @param cb buffer to accumulate the result
240: * @param xvi roman characters, e.g. "mdclxvi" and "MDCLXVI"
241: * @param count the number to convert,
242: */
243: private void romanize(CharBuffer cb, String xvi, int count) {
244: if (count <= 0)
245: throw new RuntimeException();
246:
247: if (count > 5000) {
248: cb.append(count);
249: return;
250: }
251:
252: for (; count > 1000; count -= 1000)
253: cb.append(xvi.charAt(0));
254:
255: romanize(cb, xvi.charAt(0), xvi.charAt(1), xvi.charAt(2),
256: count / 100);
257: count %= 100;
258: romanize(cb, xvi.charAt(2), xvi.charAt(3), xvi.charAt(4),
259: count / 10);
260: count %= 10;
261: romanize(cb, xvi.charAt(4), xvi.charAt(5), xvi.charAt(6), count);
262: }
263:
264: /**
265: * Convert a single decimal digit to a roman number
266: *
267: * @param cb buffer to accumulate the result.
268: * @param x character for the tens number
269: * @param v character for the fives number
270: * @param i character for the ones number
271: * @param count digit to convert
272: */
273: private void romanize(CharBuffer cb, char x, char v, char i,
274: int count) {
275: switch (count) {
276: case 0:
277: break;
278: case 1:
279: cb.append(i);
280: break;
281: case 2:
282: cb.append(i);
283: cb.append(i);
284: break;
285: case 3:
286: cb.append(i);
287: cb.append(i);
288: cb.append(i);
289: break;
290: case 4:
291: cb.append(i);
292: cb.append(v);
293: break;
294: case 5:
295: cb.append(v);
296: break;
297: case 6:
298: cb.append(v);
299: cb.append(i);
300: break;
301: case 7:
302: cb.append(v);
303: cb.append(i);
304: cb.append(i);
305: break;
306: case 8:
307: cb.append(v);
308: cb.append(i);
309: cb.append(i);
310: cb.append(i);
311: break;
312: case 9:
313: cb.append(i);
314: cb.append(x);
315: break;
316:
317: default:
318: throw new RuntimeException();
319: }
320: }
321:
322: /**
323: * Format an alphabetic number. Only English encodings are supported.
324: *
325: * @param cb buffer to accumulate results
326: * @param a starting character
327: * @param count number to convert
328: */
329: private void formatAlpha(CharBuffer cb, char a, int count) {
330: if (count <= 0)
331: throw new RuntimeException();
332:
333: int index = cb.length();
334: while (count > 0) {
335: count--;
336: cb.insert(index, (char) (a + count % 26));
337:
338: count /= 26;
339: }
340: }
341:
342: /**
343: * Format a decimal number.
344: *
345: * @param cb buffer to accumulate results
346: * @param code code for the zero digit
347: * @param zeroCount minimum digits
348: * @param count number to convert
349: */
350: private void formatDecimal(CharBuffer cb, int code, int zeroCount,
351: int count) {
352: int digits = 0;
353: int index = cb.length();
354: while (count > 0) {
355: if (digits > 0 && digits % groupSize == 0)
356: cb.insert(index, groupSeparator);
357: cb.insert(index, (char) (code + count % 10));
358: count /= 10;
359: digits++;
360: }
361: while (cb.length() - index < zeroCount) {
362: cb.insert(index, (char) code);
363: }
364: }
365: }
|