5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
9 // (C) 2003 Atsushi Enomoto
13 using System.Collections;
15 using System.Xml.XPath;
20 namespace Mono.Xml.Xsl.Operations {
21 public class XslNumber : XslCompiledElement {
24 // level = "single" | "multiple" | "any"
25 XslNumberingLevel level;
30 // value = number-expression
31 XPathExpression value;
32 // format = { string }
36 // letter-value = { "alphabetic" | "traditional" }
38 // grouping-separator = { char }
39 XslAvt groupingSeparator;
40 // grouping-size = { number } />
43 public XslNumber (Compiler c) : base (c) {}
45 protected override void Compile (Compiler c)
47 switch (c.GetAttribute ("level"))
50 level = XslNumberingLevel.Single;
53 level = XslNumberingLevel.Multiple;
56 level = XslNumberingLevel.Any;
61 level = XslNumberingLevel.Single; // single == default
65 count = c.CompilePattern (c.GetAttribute ("count"));
66 from = c.CompilePattern (c.GetAttribute ("from"));
67 value = c.CompileExpression (c.GetAttribute ("value"));
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");
76 public override void Evaluate (XslTransformProcessor p)
78 string formatted = GetFormat (p);
79 if (formatted != String.Empty)
80 p.Out.WriteString (formatted);
83 XslNumberFormatter GetNumberFormatter (XslTransformProcessor p)
85 string formatStr = "1";
87 string letterValue = null;
88 char groupingSeparatorChar = '\0';
91 if (this.format != null)
92 formatStr = this.format.Evaluate (p);
94 if (this.lang != null)
95 lang = this.lang.Evaluate (p);
97 if (this.letterValue != null)
98 letterValue = this.letterValue.Evaluate (p);
100 if (groupingSeparator != null)
101 groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0];
103 if (this.groupingSize != null)
104 groupingSize = int.Parse (this.groupingSize.Evaluate (p));
106 return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, groupingSize);
109 string GetFormat (XslTransformProcessor p)
111 XslNumberFormatter nf = GetNumberFormatter (p);
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);
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:
127 return nf.Format (hit, hit != 0);
129 throw new Exception ("Should not get here");
133 int [] NumberMultiple (XslTransformProcessor p)
135 ArrayList nums = new ArrayList ();
136 XPathNavigator n = p.CurrentNode.Clone ();
138 bool foundFrom = false;
141 if (MatchesFrom (n, p)) {
146 if (MatchesCount (n, p)) {
148 while (n.MoveToPrevious ()) {
149 if (MatchesCount (n, p)) i++;
153 } while (n.MoveToParent ());
155 if (!foundFrom) return new int [0];
157 int [] ret = new int [nums.Count];
158 int pos = nums.Count;
159 foreach (int num in nums)
165 int NumberAny (XslTransformProcessor p)
168 XPathNavigator n = p.CurrentNode.Clone ();
170 bool countable = (from == null);
172 if (from != null && MatchesFrom (n, p)) {
176 // Here this *else* is important
177 else if (countable && MatchesCount (n, p))
179 if (n.IsSamePosition (p.CurrentNode))
182 if (!n.MoveToFirstChild ()) {
183 while (!n.MoveToNext ()) {
184 if (!n.MoveToParent ()) // returned to Root
191 int NumberSingle (XslTransformProcessor p)
193 XPathNavigator n = p.CurrentNode.Clone ();
195 while (!MatchesCount (n, p)) {
196 if (from != null && MatchesFrom (n, p))
199 if (!n.MoveToParent ())
204 XPathNavigator tmp = n.Clone ();
205 if (MatchesFrom (tmp, p))
206 // Was not desc of closest matches from
210 while (tmp.MoveToParent ())
211 if (MatchesFrom (tmp, p)) {
215 // not desc of matches from
221 while (n.MoveToPrevious ()) {
222 if (MatchesCount (n, p)) i++;
228 bool MatchesCount (XPathNavigator item, XslTransformProcessor p)
231 return item.NodeType == p.CurrentNode.NodeType &&
232 item.LocalName == p.CurrentNode.LocalName &&
233 item.NamespaceURI == p.CurrentNode.NamespaceURI;
235 return p.Matches (count, item);
238 bool MatchesFrom (XPathNavigator item, XslTransformProcessor p)
241 return item.NodeType == XPathNodeType.Root;
243 return p.Matches (from, item);
246 class XslNumberFormatter {
247 string firstSep, lastSep;
248 ArrayList fmtList = new ArrayList ();
250 public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize)
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));
256 NumberFormatterScanner s = new NumberFormatterScanner (format);
261 firstSep = s.Advance (false);
262 itm = s.Advance (true);
264 if (itm == null) { // Only separator is specified
267 fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize));
269 // The first format item.
270 fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize));
272 sep = s.Advance (false);
273 itm = s.Advance (true);
278 fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize));
279 } while (itm != null);
284 // return the format for a single value, ie, if using Single or Any
285 public string Format (int value)
287 return Format (value, true);
290 public string Format (int value, bool formatContent)
292 StringBuilder b = new StringBuilder ();
293 if (firstSep != null) b.Append (firstSep);
295 ((FormatItem)fmtList [0]).Format (b, value);
296 if (lastSep != null) b.Append (lastSep);
298 return b.ToString ();
301 // format for an array of numbers.
302 public string Format (int [] values)
304 StringBuilder b = new StringBuilder ();
305 if (firstSep != null) b.Append (firstSep);
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]);
314 if (formatIndex < formatMax)
317 for (int i = 1; i < values.Length; i++) {
318 FormatItem itm = (FormatItem)fmtList [formatIndex];
322 if (formatIndex < formatMax)
326 if (lastSep != null) b.Append (lastSep);
328 return b.ToString ();
331 class NumberFormatterScanner {
335 public NumberFormatterScanner (string fmt) {
340 public string Advance (bool alphaNum)
343 while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum))
349 return fmt.Substring (start, pos - start);
353 abstract class FormatItem {
354 public readonly string sep;
355 public FormatItem (string sep)
360 public abstract void Format (StringBuilder b, int num);
362 public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize)
366 default: // See XSLT 1.0 spec 7.7.1.
368 return new DigitItem (sep, item.Length, gpSep, gpSize);
370 return new AlphaItem (sep, false);
372 return new AlphaItem (sep, true);
374 return new RomanItem (sep, false);
376 return new RomanItem (sep, true);
381 class AlphaItem : FormatItem {
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'};
386 public AlphaItem (string sep, bool uc) : base (sep)
391 public override void Format (StringBuilder b, int num)
393 alphaSeq (b, num, uc ? ucl : lcl);
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]);
403 class RomanItem : FormatItem {
405 public RomanItem (string sep, bool uc) : base (sep)
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 };
416 public override void Format (StringBuilder b, int num)
418 if (num < 1 || num > 4999) {
423 for (int i = 0; i < decValues.Length; i++) {
424 while (decValues [i] <= num) {
426 b.Append (ucrDigits [i]);
428 b.Append (lcrDigits [i]);
430 num -= decValues [i];
437 class DigitItem : FormatItem {
438 System.Globalization.NumberFormatInfo nfi;
439 int decimalSectionLength;
440 StringBuilder numberBuilder;
442 public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep)
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};
451 decimalSectionLength = len;
454 public override void Format (StringBuilder b, int num)
456 string number = num.ToString ("N", nfi);
457 int len = decimalSectionLength;
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;
473 public enum XslNumberingLevel