1 /* AbstractNumberNode.java --
2    Copyright (C) 2004 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package gnu.xml.transform;
39 
40 import gnu.java.lang.CPStringBuilder;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import javax.xml.namespace.QName;
46 import javax.xml.transform.TransformerException;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.DocumentFragment;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.Text;
51 import gnu.xml.xpath.Expr;
52 
53 /**
54  * A template node representing the XSL <code>number</code> instruction.
55  *
56  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
57  */
58 abstract class AbstractNumberNode
59   extends TemplateNode
60 {
61 
62   static final int ALPHABETIC = 0;
63   static final int TRADITIONAL = 1;
64 
65   final TemplateNode format;
66   final String lang;
67   final int letterValue;
68   final String groupingSeparator;
69   final int groupingSize;
70 
AbstractNumberNode(TemplateNode format, String lang, int letterValue, String groupingSeparator, int groupingSize)71   AbstractNumberNode(TemplateNode format, String lang,
72                      int letterValue, String groupingSeparator,
73                      int groupingSize)
74   {
75     this.format = format;
76     this.lang = lang;
77     this.letterValue = letterValue;
78     this.groupingSeparator = groupingSeparator;
79     this.groupingSize = groupingSize;
80   }
81 
doApply(Stylesheet stylesheet, QName mode, Node context, int pos, int len, Node parent, Node nextSibling)82   void doApply(Stylesheet stylesheet, QName mode,
83                Node context, int pos, int len,
84                Node parent, Node nextSibling)
85     throws TransformerException
86   {
87     Document doc = (parent instanceof Document) ? (Document) parent :
88       parent.getOwnerDocument();
89     DocumentFragment fragment = doc.createDocumentFragment();
90     format.apply(stylesheet, mode, context, pos, len, fragment, null);
91     String f = Expr._string(context, Collections.singleton(fragment));
92     String value = format(f, compute(stylesheet, context, pos, len));
93     Text text = doc.createTextNode(value);
94     if (nextSibling != null)
95       {
96         parent.insertBefore(text, nextSibling);
97       }
98     else
99       {
100         parent.appendChild(text);
101       }
102     // xsl:number doesn't process children
103     if (next != null)
104       {
105         next.apply(stylesheet, mode,
106                    context, pos, len,
107                    parent, nextSibling);
108       }
109   }
110 
format(String format, int[] number)111   String format(String format, int[] number)
112   {
113     if (number.length == 0)
114       {
115         return "";
116       }
117     int start = 0, end = 0, len = format.length(); // region of format
118     // Tokenize
119     List tokens = new ArrayList((number.length * 2) + 1);
120     List types = new ArrayList(tokens.size());
121     while (end < len)
122       {
123         while (end < len && !isAlphanumeric(format.charAt(end)))
124           {
125             end++;
126           }
127         if (end > start)
128           {
129             tokens.add(format.substring(start, end));
130             types.add(Boolean.FALSE);
131           }
132         start = end;
133         while (end < len && isAlphanumeric(format.charAt(end)))
134           {
135             end++;
136           }
137         if (end > start)
138           {
139             tokens.add(format.substring(start, end));
140             types.add(Boolean.TRUE);
141           }
142         start = end;
143       }
144     // Process tokens
145     CPStringBuilder buf = new CPStringBuilder();
146     len = tokens.size();
147     int pos = 0;
148     for (int i = 0; i < len; i++)
149       {
150         String token = (i < 0) ? "." : (String) tokens.get(i);
151         boolean alpha = (i < 0) ? true :
152           ((Boolean) types.get(i)).booleanValue();
153         if (!alpha)
154           {
155             buf.append(token);
156           }
157         else
158           {
159             if (pos < number.length)
160               {
161                 format(buf, number[pos++], token);
162                 if (((i + 1 == len) || (i + 2 == len)) &&
163                     (pos < number.length))
164                   {
165                     // More numbers than tokens, reuse last token
166                     i -= 2;
167                   }
168               }
169             if (pos == number.length && i < (len - 2))
170               {
171                 // No more numbers. Skip to the end...
172                 i = len - 2;
173                 if (((Boolean) types.get(i + 1)).booleanValue())
174                   {
175                     // number formatting token, ignore
176                     i++;
177                   }
178               }
179           }
180       }
181     //System.err.println("format: '"+format+"' "+asList(number)+" = '"+buf.toString()+"'");
182     return buf.toString();
183   }
184 
185   /*List asList(int[] number)
186     {
187       List l = new ArrayList();
188       for (int i = 0; i < number.length; i++)
189         l.add(new Integer(number[i]));
190       return l;
191     }*/
192 
format(CPStringBuilder buf, int number, String formatToken)193   void format(CPStringBuilder buf, int number, String formatToken)
194   {
195     int len = formatToken.length();
196     char c = formatToken.charAt(len - 1);
197     if (Character.digit(c, 10) == 1)
198       {
199         // Check preceding characters
200         for (int i = len - 2; i >= 0; i--)
201           {
202             if (formatToken.charAt(i) != (c - 1))
203               {
204                 format(buf, number, "1");
205                 return;
206               }
207           }
208         // Decimal representation
209         String val = Integer.toString(number);
210         for (int d = len - val.length(); d > 0; d--)
211           {
212             buf.append('0');
213           }
214         buf.append(val);
215       }
216     else if ("A".equals(formatToken))
217       {
218         buf.append(alphabetic('@', number));
219       }
220     else if ("a".equals(formatToken))
221       {
222         buf.append(alphabetic('`', number));
223       }
224     else if ("i".equals(formatToken))
225       {
226         buf.append(roman(false, number));
227       }
228     else if ("I".equals(formatToken))
229       {
230         buf.append(roman(true, number));
231       }
232     else
233       {
234         // Unknown numbering sequence
235         format(buf, number, "1");
236       }
237   }
238 
isAlphanumeric(char c)239   static final boolean isAlphanumeric(char c)
240   {
241     switch (Character.getType(c))
242       {
243       case Character.DECIMAL_DIGIT_NUMBER: // Nd
244       case Character.LETTER_NUMBER: // Nl
245       case Character.OTHER_NUMBER: // No
246       case Character.UPPERCASE_LETTER: // Lu
247       case Character.LOWERCASE_LETTER: // Ll
248       case Character.TITLECASE_LETTER: // Lt
249       case Character.MODIFIER_LETTER: // Lm
250       case Character.OTHER_LETTER: // Lo
251         return true;
252       default:
253         return false;
254       }
255   }
256 
alphabetic(char offset, int number)257   static final String alphabetic(char offset, int number)
258   {
259     CPStringBuilder buf = new CPStringBuilder();
260     while (number > 0)
261       {
262         int r = number % 26;
263         number = number / 26;
264         buf.insert(0, (char) (offset + r));
265       }
266     return buf.toString();
267   }
268 
269   static final int[] roman_numbers = {1, 5, 10, 50, 100, 500, 1000};
270   static final char[] roman_chars = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
271 
roman(boolean upper, int number)272   static final String roman(boolean upper, int number)
273   {
274     CPStringBuilder buf = new CPStringBuilder();
275     for (int pos = roman_numbers.length - 1; pos >= 0; pos -= 2)
276       {
277         int f = number / roman_numbers[pos];
278         if (f != 0)
279           {
280             number = number % (f * roman_numbers[pos]);
281           }
282         if (f > 4 && f < 9)
283           {
284             buf.append(roman_chars[pos + 1]);
285             f -= 5;
286           }
287         if (f == 4)
288           {
289             buf.append(roman_chars[pos]);
290             buf.append(roman_chars[pos + 1]);
291           }
292         else if (f == 9)
293           {
294             buf.append(roman_chars[pos]);
295             buf.append(roman_chars[pos + 2]);
296           }
297         else
298           {
299             for (; f > 0; f--)
300               {
301                 buf.append(roman_chars[pos]);
302               }
303           }
304       }
305     return upper ? buf.toString().toUpperCase() : buf.toString();
306   }
307 
compute(Stylesheet stylesheet, Node context, int pos, int len)308   abstract int[] compute(Stylesheet stylesheet, Node context, int pos, int len)
309     throws TransformerException;
310 
references(QName var)311   public boolean references(QName var)
312   {
313     if (format.references(var))
314       {
315         return true;
316       }
317     return super.references(var);
318   }
319 
toString()320   public String toString()
321   {
322     CPStringBuilder buf = new CPStringBuilder("number");
323     buf.append('[');
324     buf.append("format=");
325     buf.append(format);
326     buf.append(']');
327     return buf.toString();
328   }
329 
330 }
331