Copied remotely
[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 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections;
35 using System.Globalization;
36 using System.Xml;
37 using System.Xml.XPath;
38 using System.Xml.Xsl;
39 using System.Text;
40 using Mono.Xml.XPath;
41
42 namespace Mono.Xml.Xsl.Operations {
43         internal class XslNumber : XslCompiledElement {
44                 
45                 // <xsl:number
46                 //   level = "single" | "multiple" | "any"
47                 XslNumberingLevel level;
48                 //   count = pattern
49                 Pattern count;
50                 //   from = pattern
51                 Pattern from;
52                 //   value = number-expression
53                 XPathExpression value;
54                 //   format = { string }
55                 XslAvt format;
56                 //   lang = { nmtoken }
57                 XslAvt lang;
58                 //   letter-value = { "alphabetic" | "traditional" }
59                 XslAvt letterValue;
60                 //   grouping-separator = { char }
61                 XslAvt groupingSeparator;
62                 //   grouping-size = { number } />
63                 XslAvt groupingSize;
64                 
65                 public XslNumber (Compiler c) : base (c) {}
66                 
67                 protected override void Compile (Compiler c)
68                 {
69                         switch (c.GetAttribute ("level"))
70                         {
71                         case "single":
72                                 level = XslNumberingLevel.Single;
73                                 break;
74                         case "multiple":
75                                 level = XslNumberingLevel.Multiple;
76                                 break;
77                         case "any":
78                                 level = XslNumberingLevel.Any;
79                                 break;
80                         case null:
81                         case "":
82                         default:
83                                 level = XslNumberingLevel.Single; // single == default
84                                 break;
85                         }
86                         
87                         count = c.CompilePattern (c.GetAttribute ("count"), c.Input);
88                         from = c.CompilePattern (c.GetAttribute ("from"), c.Input);
89                         value = c.CompileExpression (c.GetAttribute ("value"));
90                         
91                         format = c.ParseAvtAttribute ("format");
92                         lang = c.ParseAvtAttribute ("lang");
93                         letterValue = c.ParseAvtAttribute ("letter-value");
94                         groupingSeparator = c.ParseAvtAttribute ("grouping-separator");
95                         groupingSize = c.ParseAvtAttribute ("grouping-size");
96                 }
97
98                 public override void Evaluate (XslTransformProcessor p)
99                 {
100                         string formatted = GetFormat (p);
101                         if (formatted != String.Empty)
102                                 p.Out.WriteString (formatted);
103                 }
104                 
105                 XslNumberFormatter GetNumberFormatter (XslTransformProcessor p)
106                 {
107                         string formatStr = "1";
108                         string lang = null;
109                         string letterValue = null;
110                         char groupingSeparatorChar = '\0';
111                         int groupingSize = 0;
112                         
113                         if (this.format != null)
114                                 formatStr = this.format.Evaluate (p);
115                         
116                         if (this.lang != null)
117                                 lang = this.lang.Evaluate (p);
118                         
119                         if (this.letterValue != null)
120                                 letterValue = this.letterValue.Evaluate (p);
121                         
122                         if (groupingSeparator != null)
123                                 groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0];
124                         
125                         if (this.groupingSize != null)
126                                 groupingSize = int.Parse (this.groupingSize.Evaluate (p), CultureInfo.InvariantCulture);
127                         
128                         return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, groupingSize);
129                 }
130                 
131                 string GetFormat (XslTransformProcessor p)
132                 {
133                         XslNumberFormatter nf = GetNumberFormatter (p);
134                         
135                         if (this.value != null) {
136                                 double result = p.EvaluateNumber (this.value);
137                                 result = (int) ((result - (int) result >= 0.5) ? result + 1 : result);
138                                 return nf.Format ((int) result);
139                         }
140                         
141                         switch (this.level) {
142                         case XslNumberingLevel.Single:
143                                 int hit = NumberSingle (p);
144                                 return nf.Format (hit, hit != 0);
145                         case XslNumberingLevel.Multiple:
146                                 return nf.Format (NumberMultiple (p));
147                         case XslNumberingLevel.Any:
148                                 hit = NumberAny (p);
149                                 return nf.Format (hit, hit != 0);
150                         default:
151                                 throw new XsltException ("Should not get here", null, p.CurrentNode);
152                         }
153                 }
154                 
155                 int [] NumberMultiple (XslTransformProcessor p)
156                 {
157                         ArrayList nums = new ArrayList ();
158                         XPathNavigator n = p.CurrentNode.Clone ();
159                         
160                         bool foundFrom = false;
161                         
162                         do {
163                                 if (MatchesFrom (n, p)) {
164                                         foundFrom = true;
165                                         break;
166                                 }
167                                 
168                                 if (MatchesCount (n, p)) {
169                                         int i = 1;
170                                         while (n.MoveToPrevious ()) {
171                                                 if (MatchesCount (n, p)) i++;
172                                         }
173                                         nums.Add (i);
174                                 }
175                         } while (n.MoveToParent ());
176                         
177                         if (!foundFrom) return new int [0];
178                                 
179                         int [] ret = new int [nums.Count];
180                         int pos = nums.Count;
181                         for (int i = 0; i < nums.Count; i++)
182                                 ret [--pos] = (int) nums [i];
183                         
184                         return ret;
185                 }
186
187                 int NumberAny (XslTransformProcessor p)
188                 {
189                         int i = 0;
190                         XPathNavigator n = p.CurrentNode.Clone ();
191                         n.MoveToRoot ();
192                         bool countable = (from == null);
193                         do {
194                                 if (from != null && MatchesFrom (n, p)) {
195                                         countable = true;
196                                         i = 0;
197                                 }
198                                 // Here this *else* is important
199                                 else if (countable && MatchesCount (n, p))
200                                         i++;
201                                 if (n.IsSamePosition (p.CurrentNode))
202                                         return i;
203
204                                 if (!n.MoveToFirstChild ()) {
205                                         while (!n.MoveToNext ()) {
206                                                 if (!n.MoveToParent ()) // returned to Root
207                                                         return 0;
208                                         };
209                                 }
210                         } while (true);\r
211                 }
212
213                 int NumberSingle (XslTransformProcessor p)
214                 {
215                         XPathNavigator n = p.CurrentNode.Clone ();
216                 
217                         while (!MatchesCount (n, p)) {
218                                 if (from != null && MatchesFrom (n, p))
219                                         return 0;
220                                 
221                                 if (!n.MoveToParent ())
222                                         return 0;
223                         }
224                         
225                         if (from != null) {
226                                 XPathNavigator tmp = n.Clone ();
227                                 if (MatchesFrom (tmp, p))
228                                         // Was not desc of closest matches from
229                                         return 0;
230                                 
231                                 bool found = false;
232                                 while (tmp.MoveToParent ())
233                                         if (MatchesFrom (tmp, p)) {
234                                                 found = true; break;
235                                         }
236                                 if (!found)
237                                         // not desc of matches from
238                                         return 0;
239                         }
240                         
241                         int i = 1;
242                                 
243                         while (n.MoveToPrevious ()) {
244                                 if (MatchesCount (n, p)) i++;
245                         }
246                                 
247                         return i;
248                 }
249                 
250                 bool MatchesCount (XPathNavigator item, XslTransformProcessor p)
251                 {
252                         if (count == null)
253                                 return item.NodeType == p.CurrentNode.NodeType &&
254                                         item.LocalName == p.CurrentNode.LocalName &&
255                                         item.NamespaceURI == p.CurrentNode.NamespaceURI;
256                         else
257                                 return p.Matches (count, item);
258                 }
259                 
260                 bool MatchesFrom (XPathNavigator item, XslTransformProcessor p)
261                 {
262                         if (from == null)
263                                 return item.NodeType == XPathNodeType.Root;
264                         else
265                                 return p.Matches (from, item);
266                 }
267                 
268                 class XslNumberFormatter {
269                         string firstSep, lastSep;
270                         ArrayList fmtList = new ArrayList ();
271                         
272                         public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize)
273                         {
274                                 // We dont do any i18n now, so we ignore lang and letterValue.
275                                 if (format == null || format == "")
276                                         fmtList.Add (FormatItem.GetItem (null, "1", groupingSeparator, groupingSize));
277                                 else {
278                                         NumberFormatterScanner s = new NumberFormatterScanner (format);
279                                         
280                                         string itm;
281                                         string sep = ".";
282                                         
283                                         firstSep = s.Advance (false);
284                                         itm = s.Advance (true);
285                                         
286                                         if (itm == null) { // Only separator is specified
287                                                 sep = firstSep;
288                                                 firstSep = null;
289                                                 fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize));
290                                         } else {
291                                                 // The first format item.
292                                                 fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize));
293                                                 do {
294                                                         sep = s.Advance (false);
295                                                         itm = s.Advance (true);
296                                                         if (itm == null) {
297                                                                 lastSep = sep;
298                                                                 break;
299                                                         }
300                                                         fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize));
301                                                 } while (itm != null);
302                                         }
303                                 }
304                         }
305                         
306                         // return the format for a single value, ie, if using Single or Any
307                         public string Format (int value)
308                         {
309                                 return Format (value, true);
310                         }
311
312                         public string Format (int value, bool formatContent)
313                         {
314                                 StringBuilder b = new StringBuilder ();
315                                 if (firstSep != null) b.Append (firstSep);
316                                 if (formatContent)
317                                         ((FormatItem)fmtList [0]).Format (b, value);
318                                 if (lastSep != null) b.Append (lastSep);
319
320                                 return b.ToString ();
321                         }
322                         
323                         // format for an array of numbers.
324                         public string Format (int [] values)
325                         {
326                                 StringBuilder b = new StringBuilder ();
327                                 if (firstSep != null) b.Append (firstSep);
328                                 
329                                 int formatIndex = 0;
330                                 int formatMax  = fmtList.Count - 1;
331                                 if (values.Length > 0) {
332                                         if (fmtList.Count > 0) {
333                                                 FormatItem itm = (FormatItem)fmtList [formatIndex];
334                                                 itm.Format (b, values [0]);
335                                         }
336                                         if (formatIndex < formatMax)
337                                                 formatIndex++;
338                                 }
339                                 for (int i = 1; i < values.Length; i++) {
340                                         FormatItem itm = (FormatItem)fmtList [formatIndex];
341                                         b.Append (itm.sep);
342                                         int v = values [i];
343                                         itm.Format (b, v);
344                                         if (formatIndex < formatMax)
345                                                 formatIndex++;
346                                 }
347                                 
348                                 if (lastSep != null) b.Append (lastSep);
349                                 
350                                 return b.ToString ();
351                         }
352                         
353                         class NumberFormatterScanner {
354                                 int pos = 0, len;
355                                 string fmt;
356                                 
357                                 public NumberFormatterScanner (string fmt) {
358                                         this.fmt = fmt;
359                                         len = fmt.Length;
360                                 }
361                                 
362                                 public string Advance (bool alphaNum)
363                                 {
364                                         int start = pos;
365                                         while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum))
366                                                 pos++;
367                                         
368                                         if (pos == start)
369                                                 return null;
370                                         else
371                                                 return fmt.Substring (start, pos - start);
372                                 }
373                         }
374                         
375                         abstract class FormatItem {
376                                 public readonly string sep;
377                                 public FormatItem (string sep)
378                                 {
379                                         this.sep = sep;
380                                 }
381                                 
382                                 public abstract void Format (StringBuilder b, int num);
383                                         
384                                 public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize)
385                                 {
386                                         switch (item [0])
387                                         {
388                                                 default: // See XSLT 1.0 spec 7.7.1.
389                                                 case '0': case '1':
390                                                         return new DigitItem (sep, item.Length, gpSep, gpSize);
391                                                 case 'a':
392                                                         return new AlphaItem (sep, false);
393                                                 case 'A':
394                                                         return new AlphaItem (sep, true);
395                                                 case 'i':
396                                                         return new RomanItem (sep, false);
397                                                 case 'I':
398                                                         return new RomanItem (sep, true);
399                                         }
400                                 }
401                         }
402                         
403                         class AlphaItem : FormatItem {
404                                 bool uc;
405                                 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'};
406                                 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'};
407                                 
408                                 public AlphaItem (string sep, bool uc) : base (sep)
409                                 {
410                                         this.uc = uc;
411                                 }
412                                 
413                                 public override void Format (StringBuilder b, int num)
414                                 {
415                                         alphaSeq (b, num, uc ? ucl : lcl);
416                                 }
417                                 
418                                 static void alphaSeq (StringBuilder b, int n, char [] alphabet) {
419                                         if (n > alphabet.Length)
420                                                 alphaSeq (b, (n-1) / alphabet.Length, alphabet);
421                                         b.Append (alphabet [(n-1) % alphabet.Length]); 
422                                 }
423                         }
424                         
425                         class RomanItem : FormatItem {
426                                 bool uc;
427                                 public RomanItem (string sep, bool uc) : base (sep)
428                                 {
429                                         this.uc = uc;
430                                 }
431                                 static readonly string [] ucrDigits =
432                                 { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
433                                 static readonly string [] lcrDigits =
434                                 { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };
435                                 static readonly int [] decValues =
436                                 {1000, 900 , 500, 400 , 100, 90  , 50 , 40  , 10 , 9   , 5  , 4   , 1   };
437                                 
438                                 public override void Format (StringBuilder b, int num)
439                                 {
440                                         if (num < 1 || num > 4999) {
441                                                 b.Append (num);
442                                                 return;
443                                         }
444                                         
445                                         for (int i = 0; i < decValues.Length; i++) {
446                                                 while (decValues [i] <= num) {
447                                                         if (uc)
448                                                                 b.Append (ucrDigits [i]);
449                                                         else
450                                                                 b.Append (lcrDigits [i]);
451                                                         
452                                                         num -= decValues [i];
453                                                 }
454                                                 if (num == 0) break;
455                                         }
456                                 }
457                         }
458                         
459                         class DigitItem : FormatItem {
460                                 NumberFormatInfo nfi;
461                                 int decimalSectionLength;
462                                 StringBuilder numberBuilder;
463                                 
464                                 public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep)
465                                 {
466                                         nfi = new NumberFormatInfo  ();
467                                         nfi.NumberDecimalDigits = 0;
468                                         if (gpSep != '\0' && gpSize > 0) {
469                                                 // ignored if either of them doesn't exist.
470                                                 nfi.NumberGroupSeparator = gpSep.ToString ();
471                                                 nfi.NumberGroupSizes = new int [] {gpSize};
472                                         }
473                                         decimalSectionLength = len;
474                                 }
475                                 
476                                 public override void Format (StringBuilder b, int num)
477                                 {
478                                         string number = num.ToString ("N", nfi);
479                                         int len = decimalSectionLength;
480                                         if (len > 1) {
481                                                 if (numberBuilder == null)
482                                                         numberBuilder = new StringBuilder ();
483                                                 for (int i = len; i > number.Length; i--)
484                                                         numberBuilder.Append ('0');
485                                                 numberBuilder.Append (number.Length <= len ? number : number.Substring (number.Length - len, len));
486                                                 number = numberBuilder.ToString ();
487                                                 numberBuilder.Length = 0;
488                                         }
489                                         b.Append (number);
490                                 }
491                         }
492                 }
493         }
494         
495         internal enum XslNumberingLevel
496         {
497                 Single,
498                 Multiple,
499                 Any
500         }
501 }