5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
9 // (C) 2003 Atsushi Enomoto
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
34 using System.Collections;
35 using System.Globalization;
37 using System.Xml.XPath;
42 namespace Mono.Xml.Xsl.Operations {
43 internal class XslNumber : XslCompiledElement {
46 // level = "single" | "multiple" | "any"
47 XslNumberingLevel level;
52 // value = number-expression
53 XPathExpression value;
54 // format = { string }
58 // letter-value = { "alphabetic" | "traditional" }
60 // grouping-separator = { char }
61 XslAvt groupingSeparator;
62 // grouping-size = { number } />
65 public XslNumber (Compiler c) : base (c) {}
67 protected override void Compile (Compiler c)
69 switch (c.GetAttribute ("level"))
72 level = XslNumberingLevel.Single;
75 level = XslNumberingLevel.Multiple;
78 level = XslNumberingLevel.Any;
83 level = XslNumberingLevel.Single; // single == default
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"));
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");
98 public override void Evaluate (XslTransformProcessor p)
100 string formatted = GetFormat (p);
101 if (formatted != String.Empty)
102 p.Out.WriteString (formatted);
105 XslNumberFormatter GetNumberFormatter (XslTransformProcessor p)
107 string formatStr = "1";
109 string letterValue = null;
110 char groupingSeparatorChar = '\0';
111 int groupingSize = 0;
113 if (this.format != null)
114 formatStr = this.format.Evaluate (p);
116 if (this.lang != null)
117 lang = this.lang.Evaluate (p);
119 if (this.letterValue != null)
120 letterValue = this.letterValue.Evaluate (p);
122 if (groupingSeparator != null)
123 groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0];
125 if (this.groupingSize != null)
126 groupingSize = int.Parse (this.groupingSize.Evaluate (p), CultureInfo.InvariantCulture);
128 return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, groupingSize);
131 string GetFormat (XslTransformProcessor p)
133 XslNumberFormatter nf = GetNumberFormatter (p);
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);
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:
149 return nf.Format (hit, hit != 0);
151 throw new XsltException ("Should not get here", null, p.CurrentNode);
155 int [] NumberMultiple (XslTransformProcessor p)
157 ArrayList nums = new ArrayList ();
158 XPathNavigator n = p.CurrentNode.Clone ();
160 bool foundFrom = false;
163 if (MatchesFrom (n, p)) {
168 if (MatchesCount (n, p)) {
170 while (n.MoveToPrevious ()) {
171 if (MatchesCount (n, p)) i++;
175 } while (n.MoveToParent ());
177 if (!foundFrom) return new int [0];
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];
187 int NumberAny (XslTransformProcessor p)
190 XPathNavigator n = p.CurrentNode.Clone ();
192 bool countable = (from == null);
194 if (from != null && MatchesFrom (n, p)) {
198 // Here this *else* is important
199 else if (countable && MatchesCount (n, p))
201 if (n.IsSamePosition (p.CurrentNode))
204 if (!n.MoveToFirstChild ()) {
205 while (!n.MoveToNext ()) {
206 if (!n.MoveToParent ()) // returned to Root
213 int NumberSingle (XslTransformProcessor p)
215 XPathNavigator n = p.CurrentNode.Clone ();
217 while (!MatchesCount (n, p)) {
218 if (from != null && MatchesFrom (n, p))
221 if (!n.MoveToParent ())
226 XPathNavigator tmp = n.Clone ();
227 if (MatchesFrom (tmp, p))
228 // Was not desc of closest matches from
232 while (tmp.MoveToParent ())
233 if (MatchesFrom (tmp, p)) {
237 // not desc of matches from
243 while (n.MoveToPrevious ()) {
244 if (MatchesCount (n, p)) i++;
250 bool MatchesCount (XPathNavigator item, XslTransformProcessor p)
253 return item.NodeType == p.CurrentNode.NodeType &&
254 item.LocalName == p.CurrentNode.LocalName &&
255 item.NamespaceURI == p.CurrentNode.NamespaceURI;
257 return p.Matches (count, item);
260 bool MatchesFrom (XPathNavigator item, XslTransformProcessor p)
263 return item.NodeType == XPathNodeType.Root;
265 return p.Matches (from, item);
268 class XslNumberFormatter {
269 string firstSep, lastSep;
270 ArrayList fmtList = new ArrayList ();
272 public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize)
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));
278 NumberFormatterScanner s = new NumberFormatterScanner (format);
283 firstSep = s.Advance (false);
284 itm = s.Advance (true);
286 if (itm == null) { // Only separator is specified
289 fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize));
291 // The first format item.
292 fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize));
294 sep = s.Advance (false);
295 itm = s.Advance (true);
300 fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize));
301 } while (itm != null);
306 // return the format for a single value, ie, if using Single or Any
307 public string Format (int value)
309 return Format (value, true);
312 public string Format (int value, bool formatContent)
314 StringBuilder b = new StringBuilder ();
315 if (firstSep != null) b.Append (firstSep);
317 ((FormatItem)fmtList [0]).Format (b, value);
318 if (lastSep != null) b.Append (lastSep);
320 return b.ToString ();
323 // format for an array of numbers.
324 public string Format (int [] values)
326 StringBuilder b = new StringBuilder ();
327 if (firstSep != null) b.Append (firstSep);
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]);
336 if (formatIndex < formatMax)
339 for (int i = 1; i < values.Length; i++) {
340 FormatItem itm = (FormatItem)fmtList [formatIndex];
344 if (formatIndex < formatMax)
348 if (lastSep != null) b.Append (lastSep);
350 return b.ToString ();
353 class NumberFormatterScanner {
357 public NumberFormatterScanner (string fmt) {
362 public string Advance (bool alphaNum)
365 while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum))
371 return fmt.Substring (start, pos - start);
375 abstract class FormatItem {
376 public readonly string sep;
377 public FormatItem (string sep)
382 public abstract void Format (StringBuilder b, int num);
384 public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize)
388 default: // See XSLT 1.0 spec 7.7.1.
390 return new DigitItem (sep, item.Length, gpSep, gpSize);
392 return new AlphaItem (sep, false);
394 return new AlphaItem (sep, true);
396 return new RomanItem (sep, false);
398 return new RomanItem (sep, true);
403 class AlphaItem : FormatItem {
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'};
408 public AlphaItem (string sep, bool uc) : base (sep)
413 public override void Format (StringBuilder b, int num)
415 alphaSeq (b, num, uc ? ucl : lcl);
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]);
425 class RomanItem : FormatItem {
427 public RomanItem (string sep, bool uc) : base (sep)
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 };
438 public override void Format (StringBuilder b, int num)
440 if (num < 1 || num > 4999) {
445 for (int i = 0; i < decValues.Length; i++) {
446 while (decValues [i] <= num) {
448 b.Append (ucrDigits [i]);
450 b.Append (lcrDigits [i]);
452 num -= decValues [i];
459 class DigitItem : FormatItem {
460 NumberFormatInfo nfi;
461 int decimalSectionLength;
462 StringBuilder numberBuilder;
464 public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep)
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};
473 decimalSectionLength = len;
476 public override void Format (StringBuilder b, int num)
478 string number = num.ToString ("N", nfi);
479 int len = decimalSectionLength;
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;
495 internal enum XslNumberingLevel