1 /*
2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * Licensed to the Apache Software Foundation (ASF) under one or more
6  * contributor license agreements.  See the NOTICE file distributed with
7  * this work for additional information regarding copyright ownership.
8  * The ASF licenses this file to You under the Apache License, Version 2.0
9  * (the "License"); you may not use this file except in compliance with
10  * the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package com.sun.org.apache.xalan.internal.xsltc.dom;
22 
23 import com.sun.org.apache.xalan.internal.xsltc.DOM;
24 import com.sun.org.apache.xalan.internal.xsltc.Translet;
25 import com.sun.org.apache.xml.internal.dtm.DTM;
26 import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * @author Jacek Ambroziak
32  * @author Santiago Pericas-Geertsen
33  * @author Morten Jorgensen
34  * @LastModified: Oct 2017
35  */
36 public 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 List<String> _separToks = new ArrayList<>();
56     private List<String> _formatToks = new ArrayList<>();
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 
NodeCounter(Translet translet, DOM document, DTMAxisIterator iterator)76     protected NodeCounter(Translet translet,
77               DOM document, DTMAxisIterator iterator) {
78     _translet = translet;
79     _document = document;
80     _iterator = iterator;
81     }
82 
NodeCounter(Translet translet, DOM document, DTMAxisIterator iterator, boolean hasFrom)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      */
setStartNode(int node)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      */
setValue(double value)101     public NodeCounter setValue(double value) {
102     _value = value;
103     return this;
104     }
105 
106     /**
107      * Sets formatting fields before calling formatNumbers().
108      */
setFormatting(String format, String lang, String letterValue, String groupSep, String groupSize)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      */
parseStringToAnInt(String s)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
setTokens(final String format)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.add(".");
208                          isFirst = _separFirst = false;
209                      }
210                      _formatToks.add(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.add(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.add(1, ".");
234              _nSepars++;
235          }
236          if (_separFirst) _nSepars ++;
237 
238  }
239     /**
240      * Sets formatting fields to their default values.
241      */
setDefaultFormatting()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      */
getCounter()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      */
getCounter(String format, String lang, String letterValue, String groupSep, String groupSize)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      */
matchesCount(int node)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      */
matchesFrom(int node)277     public boolean matchesFrom(int node) {
278     return false;
279     }
280 
281     /**
282      * Format a single value according to the format parameters.
283      */
formatNumbers(int value)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      */
formatNumbers(int[] values)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(_separToks.get(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(_separToks.get(s++));
315         formatValue(value, _formatToks.get(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(_separToks.get(_separToks.size() - 1));
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      */
formatValue(int value, String format, StringBuilder buffer)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 
alphaValue(int value, int min, int max)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 
romanValue(int n)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