1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xalan.internal.xsltc.dom;
23
24import java.util.Vector;
25
26import com.sun.org.apache.xalan.internal.xsltc.DOM;
27import com.sun.org.apache.xalan.internal.xsltc.Translet;
28import com.sun.org.apache.xml.internal.dtm.DTM;
29import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
30
31/**
32 * @author Jacek Ambroziak
33 * @author Santiago Pericas-Geertsen
34 * @author Morten Jorgensen
35 */
36public abstract class NodeCounter {
37    public static final int END = DTM.NULL;
38
39    protected int _node = END;
40    protected int _nodeType = DOM.FIRST_TYPE - 1;
41    protected double _value = Integer.MIN_VALUE;
42
43    public final DOM          _document;
44    public final DTMAxisIterator _iterator;
45    public final Translet     _translet;
46
47    protected String _format;
48    protected String _lang;
49    protected String _letterValue;
50    protected String _groupSep;
51    protected int    _groupSize;
52
53    private boolean _separFirst = true;
54    private boolean _separLast = false;
55    private Vector _separToks = new Vector();
56    private Vector _formatToks = new Vector();
57    private int _nSepars  = 0;
58    private int _nFormats = 0;
59
60    private final static String[] Thousands =
61        {"", "m", "mm", "mmm" };
62    private final static String[] Hundreds =
63    {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
64    private final static String[] Tens =
65    {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
66    private final static String[] Ones =
67    {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
68
69    private StringBuilder _tempBuffer = new StringBuilder();
70
71    /**
72     * Indicates if this instance of xsl:number has a from pattern.
73     */
74    protected boolean _hasFrom;
75
76    protected NodeCounter(Translet translet,
77              DOM document, DTMAxisIterator iterator) {
78    _translet = translet;
79    _document = document;
80    _iterator = iterator;
81    }
82
83    protected NodeCounter(Translet translet,
84              DOM document, DTMAxisIterator iterator, boolean hasFrom) {
85        _translet = translet;
86        _document = document;
87        _iterator = iterator;
88        _hasFrom = hasFrom;
89    }
90
91    /**
92     * Set the start node for this counter. The same <tt>NodeCounter</tt>
93     * object can be used multiple times by resetting the starting node.
94     */
95    abstract public NodeCounter setStartNode(int node);
96
97    /**
98     * If the user specified a value attribute, use this instead of
99     * counting nodes.
100     */
101    public NodeCounter setValue(double value) {
102    _value = value;
103    return this;
104    }
105
106    /**
107     * Sets formatting fields before calling formatNumbers().
108     */
109    protected void setFormatting(String format, String lang, String letterValue,
110                 String groupSep, String groupSize) {
111    _lang = lang;
112    _groupSep = groupSep;
113    _letterValue = letterValue;
114    _groupSize = parseStringToAnInt(groupSize);
115    setTokens(format);
116
117 }
118
119    /**
120     * Effectively does the same thing as Integer.parseInt(String s) except
121     * instead of throwing a NumberFormatException, it returns 0.  This method
122     * is used instead of Integer.parseInt() since it does not incur the
123     * overhead of throwing an Exception which is expensive.
124     *
125     * @param s  A String to be parsed into an int.
126     * @return  Either an int represented by the incoming String s, or 0 if
127     *          the parsing is not successful.
128     */
129    private int parseStringToAnInt(String s) {
130        if (s == null)
131            return 0;
132
133        int result = 0;
134        boolean negative = false;
135        int radix = 10, i = 0, max = s.length();
136        int limit, multmin, digit;
137
138        if (max > 0) {
139            if (s.charAt(0) == '-') {
140                negative = true;
141                limit = Integer.MIN_VALUE;
142                i++;
143            } else {
144                limit = -Integer.MAX_VALUE;
145            }
146            multmin = limit / radix;
147            if (i < max) {
148                digit = Character.digit(s.charAt(i++), radix);
149                if (digit < 0)
150                    return 0;
151                else
152                    result = -digit;
153            }
154            while (i < max) {
155                // Accumulating negatively avoids surprises near MAX_VALUE
156                digit = Character.digit(s.charAt(i++), radix);
157                if (digit < 0)
158                    return 0;
159                if (result < multmin)
160                    return 0;
161                result *= radix;
162                if (result < limit + digit)
163                    return 0;
164                result -= digit;
165            }
166        } else {
167            return 0;
168        }
169        if (negative) {
170            if (i > 1)
171                return result;
172            else /* Only got "-" */
173                return 0;
174        } else {
175            return -result;
176        }
177    }
178
179  // format == null assumed here
180 private final void setTokens(final String format){
181     if( (_format!=null) &&(format.equals(_format)) ){// has already been set
182        return;
183     }
184     _format = format;
185     // reset
186     final int length = _format.length();
187     boolean isFirst = true;
188     _separFirst = true;
189     _separLast = false;
190     _nSepars  = 0;
191     _nFormats = 0;
192     _separToks.clear() ;
193     _formatToks.clear();
194
195         /*
196          * Tokenize the format string into alphanumeric and non-alphanumeric
197          * tokens as described in M. Kay page 241.
198          */
199         for (int j = 0, i = 0; i < length;) {
200                 char c = format.charAt(i);
201                 for (j = i; Character.isLetterOrDigit(c);) {
202                     if (++i == length) break;
203             c = format.charAt(i);
204                 }
205                 if (i > j) {
206                     if (isFirst) {
207                         _separToks.addElement(".");
208                         isFirst = _separFirst = false;
209                     }
210                     _formatToks.addElement(format.substring(j, i));
211                 }
212
213                 if (i == length) break;
214
215                 c = format.charAt(i);
216                 for (j = i; !Character.isLetterOrDigit(c);) {
217                     if (++i == length) break;
218                     c = format.charAt(i);
219                     isFirst = false;
220                 }
221                 if (i > j) {
222                     _separToks.addElement(format.substring(j, i));
223                 }
224             }
225
226         _nSepars = _separToks.size();
227         _nFormats = _formatToks.size();
228         if (_nSepars > _nFormats) _separLast = true;
229
230         if (_separFirst) _nSepars--;
231         if (_separLast) _nSepars--;
232         if (_nSepars == 0) {
233             _separToks.insertElementAt(".", 1);
234             _nSepars++;
235         }
236         if (_separFirst) _nSepars ++;
237
238 }
239    /**
240     * Sets formatting fields to their default values.
241     */
242    public NodeCounter setDefaultFormatting() {
243    setFormatting("1", "en", "alphabetic", null, null);
244    return this;
245    }
246
247    /**
248     * Returns the position of <tt>node</tt> according to the level and
249     * the from and count patterns.
250     */
251    abstract public String getCounter();
252
253    /**
254     * Returns the position of <tt>node</tt> according to the level and
255     * the from and count patterns. This position is converted into a
256     * string based on the arguments passed.
257     */
258    public String getCounter(String format, String lang, String letterValue,
259                String groupSep, String groupSize) {
260    setFormatting(format, lang, letterValue, groupSep, groupSize);
261    return getCounter();
262    }
263
264    /**
265     * Returns true if <tt>node</tt> matches the count pattern. By
266     * default a node matches the count patterns if it is of the
267     * same type as the starting node.
268     */
269    public boolean matchesCount(int node) {
270    return _nodeType == _document.getExpandedTypeID(node);
271    }
272
273    /**
274     * Returns true if <tt>node</tt> matches the from pattern. By default,
275     * no node matches the from pattern.
276     */
277    public boolean matchesFrom(int node) {
278    return false;
279    }
280
281    /**
282     * Format a single value according to the format parameters.
283     */
284    protected String formatNumbers(int value) {
285    return formatNumbers(new int[] { value });
286    }
287
288    /**
289     * Format a sequence of values according to the format paramaters
290     * set by calling setFormatting().
291     */
292    protected String formatNumbers(int[] values) {
293    final int nValues = values.length;
294
295    boolean isEmpty = true;
296    for (int i = 0; i < nValues; i++)
297        if (values[i] != Integer.MIN_VALUE)
298        isEmpty = false;
299    if (isEmpty) return("");
300
301    // Format the output string using the values array and the fmt. tokens
302    boolean isFirst = true;
303    int t = 0, n = 0, s = 1;
304  _tempBuffer.setLength(0);
305    final StringBuilder buffer = _tempBuffer;
306
307    // Append separation token before first digit/letter/numeral
308    if (_separFirst) buffer.append((String)_separToks.elementAt(0));
309
310    // Append next digit/letter/numeral and separation token
311    while (n < nValues) {
312        final int value = values[n];
313        if (value != Integer.MIN_VALUE) {
314        if (!isFirst) buffer.append((String) _separToks.elementAt(s++));
315        formatValue(value, (String)_formatToks.elementAt(t++), buffer);
316        if (t == _nFormats) t--;
317        if (s >= _nSepars) s--;
318        isFirst = false;
319        }
320        n++;
321    }
322
323    // Append separation token after last digit/letter/numeral
324    if (_separLast) buffer.append((String)_separToks.lastElement());
325    return buffer.toString();
326    }
327
328    /**
329     * Format a single value based on the appropriate formatting token.
330     * This method is based on saxon (Michael Kay) and only implements
331     * lang="en".
332     */
333    private void formatValue(int value, String format, StringBuilder buffer) {
334        char c = format.charAt(0);
335
336        if (Character.isDigit(c)) {
337            char zero = (char)(c - Character.getNumericValue(c));
338
339            StringBuilder temp = buffer;
340            if (_groupSize > 0) {
341                temp = new StringBuilder();
342            }
343            String s = "";
344            int n = value;
345            while (n > 0) {
346                s = (char) ((int) zero + (n % 10)) + s;
347                n = n / 10;
348            }
349
350            for (int i = 0; i < format.length() - s.length(); i++) {
351                temp.append(zero);
352            }
353            temp.append(s);
354
355            if (_groupSize > 0) {
356                for (int i = 0; i < temp.length(); i++) {
357                    if (i != 0 && ((temp.length() - i) % _groupSize) == 0) {
358                        buffer.append(_groupSep);
359                    }
360                    buffer.append(temp.charAt(i));
361                }
362            }
363        }
364    else if (c == 'i' && !_letterValue.equals("alphabetic")) {
365            buffer.append(romanValue(value));
366        }
367    else if (c == 'I' && !_letterValue.equals("alphabetic")) {
368            buffer.append(romanValue(value).toUpperCase());
369        }
370    else {
371        int min = (int) c;
372        int max = (int) c;
373
374        // Special case for Greek alphabet
375        if (c >= 0x3b1 && c <= 0x3c9) {
376        max = 0x3c9;    // omega
377        }
378        else {
379        // General case: search for end of group
380        while (Character.isLetterOrDigit((char) (max + 1))) {
381            max++;
382        }
383        }
384            buffer.append(alphaValue(value, min, max));
385        }
386    }
387
388    private String alphaValue(int value, int min, int max) {
389        if (value <= 0) {
390        return "" + value;
391    }
392
393        int range = max - min + 1;
394        char last = (char)(((value-1) % range) + min);
395        if (value > range) {
396            return alphaValue((value-1) / range, min, max) + last;
397        }
398    else {
399            return "" + last;
400        }
401    }
402
403    private String romanValue(int n) {
404        if (n <= 0 || n > 4000) {
405        return "" + n;
406    }
407        return
408        Thousands[n / 1000] +
409        Hundreds[(n / 100) % 10] +
410        Tens[(n/10) % 10] +
411        Ones[n % 10];
412    }
413
414}
415