// // XslNumber.cs // // Authors: // Ben Maurer (bmaurer@users.sourceforge.net) // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp) // // (C) 2003 Ben Maurer // (C) 2003 Atsushi Enomoto // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Globalization; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; using System.Text; using Mono.Xml.XPath; namespace Mono.Xml.Xsl.Operations { internal class XslNumber : XslCompiledElement { // XslAvt groupingSize; public XslNumber (Compiler c) : base (c) {} protected override void Compile (Compiler c) { switch (c.GetAttribute ("level")) { case "single": level = XslNumberingLevel.Single; break; case "multiple": level = XslNumberingLevel.Multiple; break; case "any": level = XslNumberingLevel.Any; break; case null: case "": default: level = XslNumberingLevel.Single; // single == default break; } count = c.CompilePattern (c.GetAttribute ("count"), c.Input); from = c.CompilePattern (c.GetAttribute ("from"), c.Input); value = c.CompileExpression (c.GetAttribute ("value")); format = c.ParseAvtAttribute ("format"); lang = c.ParseAvtAttribute ("lang"); letterValue = c.ParseAvtAttribute ("letter-value"); groupingSeparator = c.ParseAvtAttribute ("grouping-separator"); groupingSize = c.ParseAvtAttribute ("grouping-size"); } public override void Evaluate (XslTransformProcessor p) { string formatted = GetFormat (p); if (formatted != String.Empty) p.Out.WriteString (formatted); } XslNumberFormatter GetNumberFormatter (XslTransformProcessor p) { string formatStr = "1"; string lang = null; string letterValue = null; char groupingSeparatorChar = '\0'; int groupingSize = 0; if (this.format != null) formatStr = this.format.Evaluate (p); if (this.lang != null) lang = this.lang.Evaluate (p); if (this.letterValue != null) letterValue = this.letterValue.Evaluate (p); if (groupingSeparator != null) groupingSeparatorChar = this.groupingSeparator.Evaluate (p) [0]; if (this.groupingSize != null) groupingSize = int.Parse (this.groupingSize.Evaluate (p), CultureInfo.InvariantCulture); return new XslNumberFormatter (formatStr, lang, letterValue, groupingSeparatorChar, groupingSize); } string GetFormat (XslTransformProcessor p) { XslNumberFormatter nf = GetNumberFormatter (p); if (this.value != null) { double result = p.EvaluateNumber (this.value); result = (int) ((result - (int) result >= 0.5) ? result + 1 : result); return nf.Format ((int) result); } switch (this.level) { case XslNumberingLevel.Single: int hit = NumberSingle (p); return nf.Format (hit, hit != 0); case XslNumberingLevel.Multiple: return nf.Format (NumberMultiple (p)); case XslNumberingLevel.Any: hit = NumberAny (p); return nf.Format (hit, hit != 0); default: throw new XsltException ("Should not get here", null, p.CurrentNode); } } int [] NumberMultiple (XslTransformProcessor p) { ArrayList nums = new ArrayList (); XPathNavigator n = p.CurrentNode.Clone (); bool foundFrom = false; do { if (MatchesFrom (n, p)) { foundFrom = true; break; } if (MatchesCount (n, p)) { int i = 1; while (n.MoveToPrevious ()) { if (MatchesCount (n, p)) i++; } nums.Add (i); } } while (n.MoveToParent ()); if (!foundFrom) return new int [0]; int [] ret = new int [nums.Count]; int pos = nums.Count; for (int i = 0; i < nums.Count; i++) ret [--pos] = (int) nums [i]; return ret; } int NumberAny (XslTransformProcessor p) { int i = 0; XPathNavigator n = p.CurrentNode.Clone (); n.MoveToRoot (); bool countable = (from == null); do { if (from != null && MatchesFrom (n, p)) { countable = true; i = 0; } // Here this *else* is important else if (countable && MatchesCount (n, p)) i++; if (n.IsSamePosition (p.CurrentNode)) return i; if (!n.MoveToFirstChild ()) { while (!n.MoveToNext ()) { if (!n.MoveToParent ()) // returned to Root return 0; }; } } while (true); } int NumberSingle (XslTransformProcessor p) { XPathNavigator n = p.CurrentNode.Clone (); while (!MatchesCount (n, p)) { if (from != null && MatchesFrom (n, p)) return 0; if (!n.MoveToParent ()) return 0; } if (from != null) { XPathNavigator tmp = n.Clone (); if (MatchesFrom (tmp, p)) // Was not desc of closest matches from return 0; bool found = false; while (tmp.MoveToParent ()) if (MatchesFrom (tmp, p)) { found = true; break; } if (!found) // not desc of matches from return 0; } int i = 1; while (n.MoveToPrevious ()) { if (MatchesCount (n, p)) i++; } return i; } bool MatchesCount (XPathNavigator item, XslTransformProcessor p) { if (count == null) return item.NodeType == p.CurrentNode.NodeType && item.LocalName == p.CurrentNode.LocalName && item.NamespaceURI == p.CurrentNode.NamespaceURI; else return p.Matches (count, item); } bool MatchesFrom (XPathNavigator item, XslTransformProcessor p) { if (from == null) return item.NodeType == XPathNodeType.Root; else return p.Matches (from, item); } class XslNumberFormatter { string firstSep, lastSep; ArrayList fmtList = new ArrayList (); public XslNumberFormatter (string format, string lang, string letterValue, char groupingSeparator, int groupingSize) { // We dont do any i18n now, so we ignore lang and letterValue. if (format == null || format == "") fmtList.Add (FormatItem.GetItem (null, "1", groupingSeparator, groupingSize)); else { NumberFormatterScanner s = new NumberFormatterScanner (format); string itm; string sep = "."; firstSep = s.Advance (false); itm = s.Advance (true); if (itm == null) { // Only separator is specified sep = firstSep; firstSep = null; fmtList.Add (FormatItem.GetItem (sep, "1", groupingSeparator, groupingSize)); } else { // The first format item. fmtList.Add (FormatItem.GetItem (".", itm, groupingSeparator, groupingSize)); do { sep = s.Advance (false); itm = s.Advance (true); if (itm == null) { lastSep = sep; break; } fmtList.Add (FormatItem.GetItem (sep, itm, groupingSeparator, groupingSize)); } while (itm != null); } } } // return the format for a single value, ie, if using Single or Any public string Format (int value) { return Format (value, true); } public string Format (int value, bool formatContent) { StringBuilder b = new StringBuilder (); if (firstSep != null) b.Append (firstSep); if (formatContent) ((FormatItem)fmtList [0]).Format (b, value); if (lastSep != null) b.Append (lastSep); return b.ToString (); } // format for an array of numbers. public string Format (int [] values) { StringBuilder b = new StringBuilder (); if (firstSep != null) b.Append (firstSep); int formatIndex = 0; int formatMax = fmtList.Count - 1; if (values.Length > 0) { if (fmtList.Count > 0) { FormatItem itm = (FormatItem)fmtList [formatIndex]; itm.Format (b, values [0]); } if (formatIndex < formatMax) formatIndex++; } for (int i = 1; i < values.Length; i++) { FormatItem itm = (FormatItem)fmtList [formatIndex]; b.Append (itm.sep); int v = values [i]; itm.Format (b, v); if (formatIndex < formatMax) formatIndex++; } if (lastSep != null) b.Append (lastSep); return b.ToString (); } class NumberFormatterScanner { int pos = 0, len; string fmt; public NumberFormatterScanner (string fmt) { this.fmt = fmt; len = fmt.Length; } public string Advance (bool alphaNum) { int start = pos; while ((pos < len) && (char.IsLetterOrDigit (fmt, pos) == alphaNum)) pos++; if (pos == start) return null; else return fmt.Substring (start, pos - start); } } abstract class FormatItem { public readonly string sep; public FormatItem (string sep) { this.sep = sep; } public abstract void Format (StringBuilder b, int num); public static FormatItem GetItem (string sep, string item, char gpSep, int gpSize) { switch (item [0]) { default: // See XSLT 1.0 spec 7.7.1. case '0': case '1': return new DigitItem (sep, item.Length, gpSep, gpSize); case 'a': return new AlphaItem (sep, false); case 'A': return new AlphaItem (sep, true); case 'i': return new RomanItem (sep, false); case 'I': return new RomanItem (sep, true); } } } class AlphaItem : FormatItem { bool uc; 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'}; 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'}; public AlphaItem (string sep, bool uc) : base (sep) { this.uc = uc; } public override void Format (StringBuilder b, int num) { alphaSeq (b, num, uc ? ucl : lcl); } static void alphaSeq (StringBuilder b, int n, char [] alphabet) { if (n > alphabet.Length) alphaSeq (b, (n-1) / alphabet.Length, alphabet); b.Append (alphabet [(n-1) % alphabet.Length]); } } class RomanItem : FormatItem { bool uc; public RomanItem (string sep, bool uc) : base (sep) { this.uc = uc; } static readonly string [] ucrDigits = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; static readonly string [] lcrDigits = { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" }; static readonly int [] decValues = {1000, 900 , 500, 400 , 100, 90 , 50 , 40 , 10 , 9 , 5 , 4 , 1 }; public override void Format (StringBuilder b, int num) { if (num < 1 || num > 4999) { b.Append (num); return; } for (int i = 0; i < decValues.Length; i++) { while (decValues [i] <= num) { if (uc) b.Append (ucrDigits [i]); else b.Append (lcrDigits [i]); num -= decValues [i]; } if (num == 0) break; } } } class DigitItem : FormatItem { NumberFormatInfo nfi; int decimalSectionLength; StringBuilder numberBuilder; public DigitItem (string sep, int len, char gpSep, int gpSize) : base (sep) { nfi = new NumberFormatInfo (); nfi.NumberDecimalDigits = 0; if (gpSep != '\0' && gpSize > 0) { // ignored if either of them doesn't exist. nfi.NumberGroupSeparator = gpSep.ToString (); nfi.NumberGroupSizes = new int [] {gpSize}; } decimalSectionLength = len; } public override void Format (StringBuilder b, int num) { string number = num.ToString ("N", nfi); int len = decimalSectionLength; if (len > 1) { if (numberBuilder == null) numberBuilder = new StringBuilder (); for (int i = len; i > number.Length; i--) numberBuilder.Append ('0'); numberBuilder.Append (number.Length <= len ? number : number.Substring (number.Length - len, len)); number = numberBuilder.ToString (); numberBuilder.Length = 0; } b.Append (number); } } } } internal enum XslNumberingLevel { Single, Multiple, Any } }