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