* Form.cs: Provide meaningful message when MdiParent is assigned a
[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                 // This behaves differently from Math.Round. For n + 0.5,
68                 // Math.Round() truncates, while XSLT expects ceiling.
69                 public static double Round (double n)
70                 {
71                         double f = System.Math.Floor (n);
72                         return (n - f >= 0.5) ? f + 1.0 : f;
73                 }
74
75                 protected override void Compile (Compiler c)
76                 {
77                         switch (c.GetAttribute ("level"))
78                         {
79                         case "single":
80                                 level = XslNumberingLevel.Single;
81                                 break;
82                         case "multiple":
83                                 level = XslNumberingLevel.Multiple;
84                                 break;
85                         case "any":
86                                 level = XslNumberingLevel.Any;
87                                 break;
88                         case null:
89                         case "":
90                         default:
91                                 level = XslNumberingLevel.Single; // single == default
92                                 break;
93                         }
94                         
95                         count = c.CompilePattern (c.GetAttribute ("count"), c.Input);
96                         from = c.CompilePattern (c.GetAttribute ("from"), c.Input);
97                         value = c.CompileExpression (c.GetAttribute ("value"));
98                         
99                         format = c.ParseAvtAttribute ("format");
100                         lang = c.ParseAvtAttribute ("lang");
101                         letterValue = c.ParseAvtAttribute ("letter-value");
102                         groupingSeparator = c.ParseAvtAttribute ("grouping-separator");
103                         groupingSize = c.ParseAvtAttribute ("grouping-size");
104                 }
105
106                 public override void Evaluate (XslTransformProcessor p)
107                 {
108                         string formatted = GetFormat (p);
109                         if (formatted != String.Empty)
110                                 p.Out.WriteString (formatted);
111                 }
112                 
113                 XslNumberFormatter GetNumberFormatter (XslTransformProcessor p)
114                 {
115                         string formatStr = "1";
116                         string lang = null;
117                         string letterValue = null;
118                         char groupingSeparatorChar = '\0';
119                         decimal groupingSize = 0;
120                         
121                         if (this.format != null)
122                                 formatStr = this.format.Evaluate (p);
123                         
124                         if (this.lang != null)
125                                 lang = this.lang.Evaluate (p);
126                         
127                         if (this.letterValue != null)
128                                 letterValue = this.letterValue.Evaluate (p);
129                         
130                         if (groupingSeparator != null)
131                                 groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0];
132                         
133                         if (this.groupingSize != null)
134                                 groupingSize = decimal.Parse (this.groupingSize.Evaluate (p), CultureInfo.InvariantCulture);
135                         
136                         //FIXME: Negative test compliency: .NET throws exception on negative grouping-size
137                         if (groupingSize > Int32.MaxValue || groupingSize < 1)
138                                 groupingSize = 0;
139
140                         return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, (int)groupingSize);
141                 }
142                 
143                 string GetFormat (XslTransformProcessor p)
144                 {
145                         XslNumberFormatter nf = GetNumberFormatter (p);
146                         
147                         if (this.value != null) {
148                                 double result = p.EvaluateNumber (this.value);
149                                 //Do we need to round the result here???
150                                 //result = (int) ((result - (int) result >= 0.5) ? result + 1 : result); 
151                                 return nf.Format (result);
152                         }
153                         
154                         switch (this.level) {
155                         case XslNumberingLevel.Single:
156                                 int hit = NumberSingle (p);
157                                 return nf.Format (hit, hit != 0);
158                         case XslNumberingLevel.Multiple:
159                                 return nf.Format (NumberMultiple (p));
160                         case XslNumberingLevel.Any:
161                                 hit = NumberAny (p);
162                                 return nf.Format (hit, hit != 0);
163                         default:
164                                 throw new XsltException ("Should not get here", null, p.CurrentNode);
165                         }
166                 }
167                 
168                 int [] NumberMultiple (XslTransformProcessor p)
169                 {
170                         ArrayList nums = new ArrayList ();
171                         XPathNavigator n = p.CurrentNode.Clone ();
172                         
173                         bool foundFrom = false;
174                         
175                         do {
176                                 if (MatchesFrom (n, p)) {
177                                         foundFrom = true;
178                                         break;
179                                 }
180                                 
181                                 if (MatchesCount (n, p)) {
182                                         int i = 1;
183                                         while (n.MoveToPrevious ()) {
184                                                 if (MatchesCount (n, p)) i++;
185                                         }
186                                         nums.Add (i);
187                                 }
188                         } while (n.MoveToParent ());
189                         
190                         if (!foundFrom) return new int [0];
191                                 
192                         int [] ret = new int [nums.Count];
193                         int pos = nums.Count;
194                         for (int i = 0; i < nums.Count; i++)
195                                 ret [--pos] = (int) nums [i];
196                         
197                         return ret;
198                 }
199
200                 int NumberAny (XslTransformProcessor p)
201                 {
202                         int i = 0;
203                         XPathNavigator n = p.CurrentNode.Clone ();
204                         n.MoveToRoot ();
205                         bool countable = (from == null);
206                         do {
207                                 if (from != null && MatchesFrom (n, p)) {
208                                         countable = true;
209                                         i = 0;
210                                 }
211                                 // Here this *else* is important
212                                 else if (countable && MatchesCount (n, p))
213                                         i++;
214                                 if (n.IsSamePosition (p.CurrentNode))
215                                         return i;
216
217                                 if (!n.MoveToFirstChild ()) {
218                                         while (!n.MoveToNext ()) {
219                                                 if (!n.MoveToParent ()) // returned to Root
220                                                         return 0;
221                                         };
222                                 }
223                         } while (true);
224                 }
225
226                 int NumberSingle (XslTransformProcessor p)
227                 {
228                         XPathNavigator n = p.CurrentNode.Clone ();
229                 
230                         while (!MatchesCount (n, p)) {
231                                 if (from != null && MatchesFrom (n, p))
232                                         return 0;
233                                 
234                                 if (!n.MoveToParent ())
235                                         return 0;
236                         }
237                         
238                         if (from != null) {
239                                 XPathNavigator tmp = n.Clone ();
240                                 if (MatchesFrom (tmp, p))
241                                         // Was not desc of closest matches from
242                                         return 0;
243                                 
244                                 bool found = false;
245                                 while (tmp.MoveToParent ())
246                                         if (MatchesFrom (tmp, p)) {
247                                                 found = true; break;
248                                         }
249                                 if (!found)
250                                         // not desc of matches from
251                                         return 0;
252                         }
253                         
254                         int i = 1;
255                                 
256                         while (n.MoveToPrevious ()) {
257                                 if (MatchesCount (n, p)) i++;
258                         }
259                                 
260                         return i;
261                 }
262                 
263                 bool MatchesCount (XPathNavigator item, XslTransformProcessor p)
264                 {
265                         if (count == null)
266                                 return item.NodeType == p.CurrentNode.NodeType &&
267                                         item.LocalName == p.CurrentNode.LocalName &&
268                                         item.NamespaceURI == p.CurrentNode.NamespaceURI;
269                         else
270                                 return p.Matches (count, item);
271                 }
272                 
273                 bool MatchesFrom (XPathNavigator item, XslTransformProcessor p)
274                 {
275                         if (from == null)
276                                 return item.NodeType == XPathNodeType.Root;
277                         else
278                                 return p.Matches (from, item);
279                 }
280                 
281                 class XslNumberFormatter {
282                         string firstSep, lastSep;
283                         ArrayList fmtList = new ArrayList ();
284                         
285                         public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize)
286                         {
287                                 // We dont do any i18n now, so we ignore lang and letterValue.
288                                 if (format == null || format == "")
289                                         fmtList.Add (FormatItem.GetItem (null, "1", groupingSeparator, groupingSize));
290                                 else {
291                                         NumberFormatterScanner s = new NumberFormatterScanner (format);
292                                         
293                                         string itm;
294                                         string sep = ".";
295                                         
296                                         firstSep = s.Advance (false);
297                                         itm = s.Advance (true);
298                                         
299                                         if (itm == null) { // Only separator is specified
300                                                 sep = firstSep;
301                                                 firstSep = null;
302                                                 fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize));
303                                         } else {
304                                                 // The first format item.
305                                                 fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize));
306                                                 do {
307                                                         sep = s.Advance (false);
308                                                         itm = s.Advance (true);
309                                                         if (itm == null) {
310                                                                 lastSep = sep;
311                                                                 break;
312                                                         }
313                                                         fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize));
314                                                 } while (itm != null);
315                                         }
316                                 }
317                         }
318                         
319                         // return the format for a single value, ie, if using Single or Any
320                         public string Format (double value)
321                         {
322                                 return Format (value, true);
323                         }
324
325                         public string Format (double value, bool formatContent)
326                         {
327                                 StringBuilder b = new StringBuilder ();
328                                 if (firstSep != null) b.Append (firstSep);
329                                 if (formatContent)
330                                         ((FormatItem)fmtList [0]).Format (b, value);
331                                 if (lastSep != null) b.Append (lastSep);
332                                 return b.ToString ();
333                         }
334                         
335                         // format for an array of numbers.
336                         public string Format (int [] values)
337                         {
338                                 StringBuilder b = new StringBuilder ();
339                                 if (firstSep != null) b.Append (firstSep);
340                                 
341                                 int formatIndex = 0;
342                                 int formatMax  = fmtList.Count - 1;
343                                 if (values.Length > 0) {
344                                         if (fmtList.Count > 0) {
345                                                 FormatItem itm = (FormatItem)fmtList [formatIndex];
346                                                 itm.Format (b, values [0]);
347                                         }
348                                         if (formatIndex < formatMax)
349                                                 formatIndex++;
350                                 }
351                                 for (int i = 1; i < values.Length; i++) {
352                                         FormatItem itm = (FormatItem)fmtList [formatIndex];
353                                         b.Append (itm.sep);
354                                         int v = values [i];
355                                         itm.Format (b, v);
356                                         if (formatIndex < formatMax)
357                                                 formatIndex++;
358                                 }
359                                 
360                                 if (lastSep != null) b.Append (lastSep);
361                                 
362                                 return b.ToString ();
363                         }
364                         
365                         class NumberFormatterScanner {
366                                 int pos = 0, len;
367                                 string fmt;
368                                 
369                                 public NumberFormatterScanner (string fmt) {
370                                         this.fmt = fmt;
371                                         len = fmt.Length;
372                                 }
373                                 
374                                 public string Advance (bool alphaNum)
375                                 {
376                                         int start = pos;
377                                         while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum))
378                                                 pos++;
379                                         
380                                         if (pos == start)
381                                                 return null;
382                                         else
383                                                 return fmt.Substring (start, pos - start);
384                                 }
385                         }
386                         
387                         abstract class FormatItem {
388                                 public readonly string sep;
389                                 public FormatItem (string sep)
390                                 {
391                                         this.sep = sep;
392                                 }
393                                 
394                                 public abstract void Format (StringBuilder b, double num);
395                                         
396                                 public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize)
397                                 {
398                                         switch (item [0])
399                                         {
400                                                 default: // See XSLT 1.0 spec 7.7.1.
401                                                         return new DigitItem (sep, 1, gpSep, gpSize);
402                                                 case '0': case '1':
403                                                         int len = 1;
404                                                         for (; len < item.Length; len++)
405                                                                 if (!Char.IsDigit (item, len))
406                                                                         break;
407                                                         return new DigitItem (sep, len, gpSep, gpSize);
408                                                 case 'a':
409                                                         return new AlphaItem (sep, false);
410                                                 case 'A':
411                                                         return new AlphaItem (sep, true);
412                                                 case 'i':
413                                                         return new RomanItem (sep, false);
414                                                 case 'I':
415                                                         return new RomanItem (sep, true);
416                                         }
417                                 }
418                         }
419                         
420                         class AlphaItem : FormatItem {
421                                 bool uc;
422                                 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'};
423                                 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'};
424                                 
425                                 public AlphaItem (string sep, bool uc) : base (sep)
426                                 {
427                                         this.uc = uc;
428                                 }
429                                 
430                                 public override void Format (StringBuilder b, double num)
431                                 {
432                                         alphaSeq (b, num, uc ? ucl : lcl);
433                                 }
434                                 
435                                 static void alphaSeq (StringBuilder b, double n, char [] alphabet) {
436                                         n = XslNumber.Round (n);
437                                         if (n == 0)
438                                                 return;
439                                         if (n > alphabet.Length)
440                                                 alphaSeq (b, System.Math.Floor ((n - 1) / alphabet.Length), alphabet);
441                                         b.Append (alphabet [((int) n - 1) % alphabet.Length]); 
442                                 }
443                         }
444                         
445                         class RomanItem : FormatItem {
446                                 bool uc;
447                                 public RomanItem (string sep, bool uc) : base (sep)
448                                 {
449                                         this.uc = uc;
450                                 }
451                                 static readonly string [] ucrDigits =
452                                 { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
453                                 static readonly string [] lcrDigits =
454                                 { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };
455                                 static readonly int [] decValues =
456                                 {1000, 900 , 500, 400 , 100, 90  , 50 , 40  , 10 , 9   , 5  , 4   , 1   };
457                                 
458                                 public override void Format (StringBuilder b, double num)
459                                 {
460                                         if (num < 1 || num > 4999) {
461                                                 b.Append (num);
462                                                 return;
463                                         }
464                                         num = XslNumber.Round (num);
465                                         for (int i = 0; i < decValues.Length; i++) {
466                                                 while (decValues [i] <= num) {
467                                                         if (uc)
468                                                                 b.Append (ucrDigits [i]);
469                                                         else
470                                                                 b.Append (lcrDigits [i]);
471                                                         
472                                                         num -= decValues [i];
473                                                 }
474                                                 if (num == 0) break;
475                                         }
476                                 }
477                         }
478                         
479                         class DigitItem : FormatItem {
480                                 NumberFormatInfo nfi;
481                                 int decimalSectionLength;
482                                 StringBuilder numberBuilder;
483                                 
484                                 public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep)
485                                 {
486                                         nfi = new NumberFormatInfo  ();
487                                         nfi.NumberDecimalDigits = 0;
488                                         nfi.NumberGroupSizes = new int [] {0};
489                                         if (gpSep != '\0' && gpSize > 0) {
490                                                 // ignored if either of them doesn't exist.
491                                                 nfi.NumberGroupSeparator = gpSep.ToString ();
492                                                 nfi.NumberGroupSizes = new int [] {gpSize};
493                                         }
494                                         decimalSectionLength = len;
495                                 }
496                                 
497                                 public override void Format (StringBuilder b, double num)
498                                 {
499                                         string number = num.ToString ("N", nfi);
500                                         int len = decimalSectionLength;
501                                         if (len > 1) {
502                                                 if (numberBuilder == null)
503                                                         numberBuilder = new StringBuilder ();
504                                                 for (int i = len; i > number.Length; i--)
505                                                         numberBuilder.Append ('0');
506                                                 numberBuilder.Append (number.Length <= len ? number : number.Substring (number.Length - len, len));
507                                                 number = numberBuilder.ToString ();
508                                                 numberBuilder.Length = 0;
509                                         }
510                                         b.Append (number);
511                                 }
512                         }
513                 }
514         }
515         
516         internal enum XslNumberingLevel
517         {
518                 Single,
519                 Multiple,
520                 Any
521         }
522 }