2004-03-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl.Operations / XslNumber.cs
1 //
2 // XslNumber.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //      
8 // (C) 2003 Ben Maurer
9 // (C) 2003 Atsushi Enomoto
10 //
11
12 using System;
13 using System.Collections;
14 using System.Xml;
15 using System.Xml.XPath;
16 using System.Xml.Xsl;
17 using System.Text;
18 using Mono.Xml.XPath;
19
20 namespace Mono.Xml.Xsl.Operations {
21         public class XslNumber : XslCompiledElement {
22                 
23                 // <xsl:number
24                 //   level = "single" | "multiple" | "any"
25                 XslNumberingLevel level;
26                 //   count = pattern
27                 Pattern count;
28                 //   from = pattern
29                 Pattern from;
30                 //   value = number-expression
31                 XPathExpression value;
32                 //   format = { string }
33                 XslAvt format;
34                 //   lang = { nmtoken }
35                 XslAvt lang;
36                 //   letter-value = { "alphabetic" | "traditional" }
37                 XslAvt letterValue;
38                 //   grouping-separator = { char }
39                 XslAvt groupingSeparator;
40                 //   grouping-size = { number } />
41                 XslAvt groupingSize;
42                 
43                 public XslNumber (Compiler c) : base (c) {}
44                 
45                 protected override void Compile (Compiler c)
46                 {
47                         switch (c.GetAttribute ("level"))
48                         {
49                         case "single":
50                                 level = XslNumberingLevel.Single;
51                                 break;
52                         case "multiple":
53                                 level = XslNumberingLevel.Multiple;
54                                 break;
55                         case "any":
56                                 level = XslNumberingLevel.Any;
57                                 break;
58                         case null:
59                         case "":
60                         default:
61                                 level = XslNumberingLevel.Single; // single == default
62                                 break;
63                         }
64                         
65                         count = c.CompilePattern (c.GetAttribute ("count"));
66                         from = c.CompilePattern (c.GetAttribute ("from"));
67                         value = c.CompileExpression (c.GetAttribute ("value"));
68                         
69                         format = c.ParseAvtAttribute ("format");
70                         lang = c.ParseAvtAttribute ("lang");
71                         letterValue = c.ParseAvtAttribute ("letter-value");
72                         groupingSeparator = c.ParseAvtAttribute ("grouping-separator");
73                         groupingSize = c.ParseAvtAttribute ("grouping-size");
74                 }
75
76                 public override void Evaluate (XslTransformProcessor p)
77                 {
78                         string formatted = GetFormat (p);
79                         if (formatted != String.Empty)
80                                 p.Out.WriteString (formatted);
81                 }
82                 
83                 XslNumberFormatter GetNumberFormatter (XslTransformProcessor p)
84                 {
85                         string formatStr = "1";
86                         string lang = null;
87                         string letterValue = null;
88                         char groupingSeparatorChar = '\0';
89                         int groupingSize = 0;
90                         
91                         if (this.format != null)
92                                 formatStr = this.format.Evaluate (p);
93                         
94                         if (this.lang != null)
95                                 lang = this.lang.Evaluate (p);
96                         
97                         if (this.letterValue != null)
98                                 letterValue = this.letterValue.Evaluate (p);
99                         
100                         if (groupingSeparator != null)
101                                 groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0];
102                         
103                         if (this.groupingSize != null)
104                                 groupingSize = int.Parse (this.groupingSize.Evaluate (p));
105                         
106                         return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, groupingSize);
107                 }
108                 
109                 string GetFormat (XslTransformProcessor p)
110                 {
111                         XslNumberFormatter nf = GetNumberFormatter (p);
112                         
113                         if (this.value != null) {
114                                 double result = p.EvaluateNumber (this.value);
115                                 result = (int) ((result - (int) result >= 0.5) ? result + 1 : result);
116                                 return nf.Format ((int) result);
117                         }
118                         
119                         switch (this.level) {
120                         case XslNumberingLevel.Single:
121                                 int hit = NumberSingle (p);
122                                 return nf.Format (hit, hit != 0);
123                         case XslNumberingLevel.Multiple:
124                                 return nf.Format (NumberMultiple (p));
125                         case XslNumberingLevel.Any:
126                                 hit = NumberAny (p);
127                                 return nf.Format (hit, hit != 0);
128                         default:
129                                 throw new Exception ("Should not get here");
130                         }
131                 }
132                 
133                 int [] NumberMultiple (XslTransformProcessor p)
134                 {
135                         ArrayList nums = new ArrayList ();
136                         XPathNavigator n = p.CurrentNode.Clone ();
137                         
138                         bool foundFrom = false;
139                         
140                         do {
141                                 if (MatchesFrom (n, p)) {
142                                         foundFrom = true;
143                                         break;
144                                 }
145                                 
146                                 if (MatchesCount (n, p)) {
147                                         int i = 1;
148                                         while (n.MoveToPrevious ()) {
149                                                 if (MatchesCount (n, p)) i++;
150                                         }
151                                         nums.Add (i);
152                                 }
153                         } while (n.MoveToParent ());
154                         
155                         if (!foundFrom) return new int [0];
156                                 
157                         int [] ret = new int [nums.Count];
158                         int pos = nums.Count;
159                         foreach (int num in nums)
160                                 ret [--pos] = num;
161                         
162                         return ret;
163                 }
164
165                 int NumberAny (XslTransformProcessor p)
166                 {
167                         int i = 0;
168                         XPathNavigator n = p.CurrentNode.Clone ();
169                         n.MoveToRoot ();
170                         bool countable = (from == null);
171                         do {
172                                 if (from != null && MatchesFrom (n, p)) {
173                                         countable = true;
174                                         i = 0;
175                                 }
176                                 // Here this *else* is important
177                                 else if (countable && MatchesCount (n, p))
178                                         i++;
179                                 if (n.IsSamePosition (p.CurrentNode))
180                                         return i;
181
182                                 if (!n.MoveToFirstChild ()) {
183                                         while (!n.MoveToNext ()) {
184                                                 if (!n.MoveToParent ()) // returned to Root
185                                                         return 0;
186                                         };
187                                 }
188                         } while (true);\r
189                 }
190
191                 int NumberSingle (XslTransformProcessor p)
192                 {
193                         XPathNavigator n = p.CurrentNode.Clone ();
194                 
195                         while (!MatchesCount (n, p)) {
196                                 if (from != null && MatchesFrom (n, p))
197                                         return 0;
198                                 
199                                 if (!n.MoveToParent ())
200                                         return 0;
201                         }
202                         
203                         if (from != null) {
204                                 XPathNavigator tmp = n.Clone ();
205                                 if (MatchesFrom (tmp, p))
206                                         // Was not desc of closest matches from
207                                         return 0;
208                                 
209                                 bool found = false;
210                                 while (tmp.MoveToParent ())
211                                         if (MatchesFrom (tmp, p)) {
212                                                 found = true; break;
213                                         }
214                                 if (!found)
215                                         // not desc of matches from
216                                         return 0;
217                         }
218                         
219                         int i = 1;
220                                 
221                         while (n.MoveToPrevious ()) {
222                                 if (MatchesCount (n, p)) i++;
223                         }
224                                 
225                         return i;
226                 }
227                 
228                 bool MatchesCount (XPathNavigator item, XslTransformProcessor p)
229                 {
230                         if (count == null)
231                                 return item.NodeType == p.CurrentNode.NodeType &&
232                                         item.LocalName == p.CurrentNode.LocalName &&
233                                         item.NamespaceURI == p.CurrentNode.NamespaceURI;
234                         else
235                                 return p.Matches (count, item);
236                 }
237                 
238                 bool MatchesFrom (XPathNavigator item, XslTransformProcessor p)
239                 {
240                         if (from == null)
241                                 return item.NodeType == XPathNodeType.Root;
242                         else
243                                 return p.Matches (from, item);
244                 }
245                 
246                 class XslNumberFormatter {
247                         string firstSep, lastSep;
248                         ArrayList fmtList = new ArrayList ();
249                         
250                         public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize)
251                         {
252                                 // We dont do any i18n now, so we ignore lang and letterValue.
253                                 if (format == null || format == "")
254                                         fmtList.Add (FormatItem.GetItem (null, "1", groupingSeparator, groupingSize));
255                                 else {
256                                         NumberFormatterScanner s = new NumberFormatterScanner (format);
257                                         
258                                         string itm;
259                                         string sep = ".";
260                                         
261                                         firstSep = s.Advance (false);
262                                         itm = s.Advance (true);
263                                         
264                                         if (itm == null) { // Only separator is specified
265                                                 sep = firstSep;
266                                                 firstSep = null;
267                                                 fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize));
268                                         } else {
269                                                 // The first format item.
270                                                 fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize));
271                                                 do {
272                                                         sep = s.Advance (false);
273                                                         itm = s.Advance (true);
274                                                         if (itm == null) {
275                                                                 lastSep = sep;
276                                                                 break;
277                                                         }
278                                                         fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize));
279                                                 } while (itm != null);
280                                         }
281                                 }
282                         }
283                         
284                         // return the format for a single value, ie, if using Single or Any
285                         public string Format (int value)
286                         {
287                                 return Format (value, true);
288                         }
289
290                         public string Format (int value, bool formatContent)
291                         {
292                                 StringBuilder b = new StringBuilder ();
293                                 if (firstSep != null) b.Append (firstSep);
294                                 if (formatContent)
295                                         ((FormatItem)fmtList [0]).Format (b, value);
296                                 if (lastSep != null) b.Append (lastSep);
297
298                                 return b.ToString ();
299                         }
300                         
301                         // format for an array of numbers.
302                         public string Format (int [] values)
303                         {
304                                 StringBuilder b = new StringBuilder ();
305                                 if (firstSep != null) b.Append (firstSep);
306                                 
307                                 int formatIndex = 0;
308                                 int formatMax  = fmtList.Count - 1;
309                                 if (values.Length > 0) {
310                                         if (fmtList.Count > 0) {
311                                                 FormatItem itm = (FormatItem)fmtList [formatIndex];
312                                                 itm.Format (b, values [0]);
313                                         }
314                                         if (formatIndex < formatMax)
315                                                 formatIndex++;
316                                 }
317                                 for (int i = 1; i < values.Length; i++) {
318                                         FormatItem itm = (FormatItem)fmtList [formatIndex];
319                                         b.Append (itm.sep);
320                                         int v = values [i];
321                                         itm.Format (b, v);
322                                         if (formatIndex < formatMax)
323                                                 formatIndex++;
324                                 }
325                                 
326                                 if (lastSep != null) b.Append (lastSep);
327                                 
328                                 return b.ToString ();
329                         }
330                         
331                         class NumberFormatterScanner {
332                                 int pos = 0, len;
333                                 string fmt;
334                                 
335                                 public NumberFormatterScanner (string fmt) {
336                                         this.fmt = fmt;
337                                         len = fmt.Length;
338                                 }
339                                 
340                                 public string Advance (bool alphaNum)
341                                 {
342                                         int start = pos;
343                                         while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum))
344                                                 pos++;
345                                         
346                                         if (pos == start)
347                                                 return null;
348                                         else
349                                                 return fmt.Substring (start, pos - start);
350                                 }
351                         }
352                         
353                         abstract class FormatItem {
354                                 public readonly string sep;
355                                 public FormatItem (string sep)
356                                 {
357                                         this.sep = sep;
358                                 }
359                                 
360                                 public abstract void Format (StringBuilder b, int num);
361                                         
362                                 public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize)
363                                 {
364                                         switch (item [0])
365                                         {
366                                                 default: // See XSLT 1.0 spec 7.7.1.
367                                                 case '0': case '1':
368                                                         return new DigitItem (sep, item.Length, gpSep, gpSize);
369                                                 case 'a':
370                                                         return new AlphaItem (sep, false);
371                                                 case 'A':
372                                                         return new AlphaItem (sep, true);
373                                                 case 'i':
374                                                         return new RomanItem (sep, false);
375                                                 case 'I':
376                                                         return new RomanItem (sep, true);
377                                         }
378                                 }
379                         }
380                         
381                         class AlphaItem : FormatItem {
382                                 bool uc;
383                                 static readonly char [] ucl = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
384                                 static readonly char [] lcl = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
385                                 
386                                 public AlphaItem (string sep, bool uc) : base (sep)
387                                 {
388                                         this.uc = uc;
389                                 }
390                                 
391                                 public override void Format (StringBuilder b, int num)
392                                 {
393                                         alphaSeq (b, num, uc ? ucl : lcl);
394                                 }
395                                 
396                                 static void alphaSeq (StringBuilder b, int n, char [] alphabet) {
397                                         if (n > alphabet.Length)
398                                                 alphaSeq (b, (n-1) / alphabet.Length, alphabet);
399                                         b.Append (alphabet [(n-1) % alphabet.Length]); 
400                                 }
401                         }
402                         
403                         class RomanItem : FormatItem {
404                                 bool uc;
405                                 public RomanItem (string sep, bool uc) : base (sep)
406                                 {
407                                         this.uc = uc;
408                                 }
409                                 static readonly string [] ucrDigits =
410                                 { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
411                                 static readonly string [] lcrDigits =
412                                 { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };
413                                 static readonly int [] decValues =
414                                 {1000, 900 , 500, 400 , 100, 90  , 50 , 40  , 10 , 9   , 5  , 4   , 1   };
415                                 
416                                 public override void Format (StringBuilder b, int num)
417                                 {
418                                         if (num < 1 || num > 4999) {
419                                                 b.Append (num);
420                                                 return;
421                                         }
422                                         
423                                         for (int i = 0; i < decValues.Length; i++) {
424                                                 while (decValues [i] <= num) {
425                                                         if (uc)
426                                                                 b.Append (ucrDigits [i]);
427                                                         else
428                                                                 b.Append (lcrDigits [i]);
429                                                         
430                                                         num -= decValues [i];
431                                                 }
432                                                 if (num == 0) break;
433                                         }
434                                 }
435                         }
436                         
437                         class DigitItem : FormatItem {
438                                 System.Globalization.NumberFormatInfo nfi;
439                                 int decimalSectionLength;
440                                 StringBuilder numberBuilder;
441                                 
442                                 public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep)
443                                 {
444                                         nfi = new System.Globalization.NumberFormatInfo  ();
445                                         nfi.NumberDecimalDigits = 0;
446                                         if (gpSep != '\0' && gpSize > 0) {
447                                                 // ignored if either of them doesn't exist.
448                                                 nfi.NumberGroupSeparator = gpSep.ToString ();
449                                                 nfi.NumberGroupSizes = new int [] {gpSize};
450                                         }
451                                         decimalSectionLength = len;
452                                 }
453                                 
454                                 public override void Format (StringBuilder b, int num)
455                                 {
456                                         string number = num.ToString ("N", nfi);
457                                         int len = decimalSectionLength;
458                                         if (len > 1) {
459                                                 if (numberBuilder == null)
460                                                         numberBuilder = new StringBuilder ();
461                                                 for (int i = len; i > number.Length; i--)
462                                                         numberBuilder.Append ('0');
463                                                 numberBuilder.Append (number.Length <= len ? number : number.Substring (number.Length - len, len));
464                                                 number = numberBuilder.ToString ();
465                                                 numberBuilder.Length = 0;
466                                         }
467                                         b.Append (number);
468                                 }
469                         }
470                 }
471         }
472         
473         public enum XslNumberingLevel
474         {
475                 Single,
476                 Multiple,
477                 Any
478         }
479 }