Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / XsltOld / NumberAction.cs
blob85038e93bad0c191ad3ad120a1a623a9b760b210
1 //------------------------------------------------------------------------------
2 // <copyright file="NumberAction.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Xml.Xsl.XsltOld {
9 using Res = System.Xml.Utils.Res;
10 using System.Diagnostics;
11 using System.Text;
12 using System.Globalization;
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Xml.XPath;
16 using System.Xml.Xsl.Runtime;
18 internal class NumberAction : ContainerAction {
19 const long msofnfcNil = 0x00000000; // no flags
20 const long msofnfcTraditional = 0x00000001; // use traditional numbering
21 const long msofnfcAlwaysFormat = 0x00000002; // if requested format is not supported, use Arabic (Western) style
23 const int cchMaxFormat = 63 ; // max size of formatted result
24 const int cchMaxFormatDecimal = 11 ; // max size of formatted decimal result (doesn't handle the case of a very large pwszSeparator or minlen)
26 internal class FormatInfo {
27 public bool isSeparator; // False for alphanumeric strings of chars
28 public NumberingSequence numSequence; // Specifies numbering sequence
29 public int length; // Minimum length of decimal numbers (if necessary, pad to left with zeros)
30 public string formatString; // Format string for separator token
32 public FormatInfo(bool isSeparator, string formatString) {
33 this.isSeparator = isSeparator;
34 this.formatString = formatString;
37 public FormatInfo() {}
40 static FormatInfo DefaultFormat = new FormatInfo(false, "0");
41 static FormatInfo DefaultSeparator = new FormatInfo(true , ".");
43 class NumberingFormat : NumberFormatterBase {
44 NumberingSequence seq;
45 int cMinLen;
46 string separator;
47 int sizeGroup;
49 internal NumberingFormat() {}
51 internal void setNumberingType(NumberingSequence seq) { this.seq = seq; }
52 //void setLangID(LID langid) {_langid = langid;}
53 //internal void setTraditional(bool fTraditional) {_grfnfc = fTraditional ? msofnfcTraditional : 0;}
54 internal void setMinLen(int cMinLen) { this.cMinLen = cMinLen; }
55 internal void setGroupingSeparator(string separator) { this.separator = separator; }
57 internal void setGroupingSize(int sizeGroup) {
58 if (0 <= sizeGroup && sizeGroup <= 9) {
59 this.sizeGroup = sizeGroup;
63 internal String FormatItem(object value) {
64 double dblVal;
66 if (value is int) {
67 dblVal = (int)value;
68 } else {
69 dblVal = XmlConvert.ToXPathDouble(value);
71 if (0.5 <= dblVal && !double.IsPositiveInfinity(dblVal)) {
72 dblVal = XmlConvert.XPathRound(dblVal);
73 } else {
74 // It is an error if the number is NaN, infinite or less than 0.5; an XSLT processor may signal the error;
75 // if it does not signal the error, it must recover by converting the number to a string as if by a call
76 // to the string function and inserting the resulting string into the result tree.
77 return XmlConvert.ToXPathString(value);
81 Debug.Assert(dblVal >= 1);
83 switch (seq) {
84 case NumberingSequence.Arabic :
85 break;
86 case NumberingSequence.UCLetter :
87 case NumberingSequence.LCLetter :
88 if (dblVal <= MaxAlphabeticValue) {
89 StringBuilder sb = new StringBuilder();
90 ConvertToAlphabetic(sb, dblVal, seq == NumberingSequence.UCLetter ? 'A' : 'a', 26);
91 return sb.ToString();
93 break;
94 case NumberingSequence.UCRoman :
95 case NumberingSequence.LCRoman:
96 if (dblVal <= MaxRomanValue) {
97 StringBuilder sb = new StringBuilder();
98 ConvertToRoman(sb, dblVal, seq == NumberingSequence.UCRoman);
99 return sb.ToString();
101 break;
104 return ConvertToArabic(dblVal, cMinLen, sizeGroup, separator);
107 static string ConvertToArabic(double val, int minLength, int groupSize, string groupSeparator) {
108 String str;
110 if (groupSize != 0 && groupSeparator != null ) {
111 NumberFormatInfo NumberFormat = new NumberFormatInfo();
112 NumberFormat.NumberGroupSizes = new int[] { groupSize };
113 NumberFormat.NumberGroupSeparator = groupSeparator;
114 if (Math.Floor(val) == val) {
115 NumberFormat.NumberDecimalDigits = 0;
117 str = val.ToString("N", NumberFormat);
119 else {
120 str = Convert.ToString(val, CultureInfo.InvariantCulture);
123 if (str.Length >= minLength) {
124 return str;
125 } else {
126 StringBuilder sb = new StringBuilder(minLength);
127 sb.Append('0', minLength - str.Length);
128 sb.Append(str);
129 return sb.ToString();
134 // States:
135 private const int OutputNumber = 2;
137 private String level;
138 private String countPattern;
139 private int countKey = Compiler.InvalidQueryKey;
140 private String from;
141 private int fromKey = Compiler.InvalidQueryKey;
142 private String value;
143 private int valueKey = Compiler.InvalidQueryKey;
144 private Avt formatAvt;
145 private Avt langAvt;
146 private Avt letterAvt;
147 private Avt groupingSepAvt;
148 private Avt groupingSizeAvt;
149 // Compile time precalculated AVTs
150 private List<FormatInfo> formatTokens;
151 private String lang;
152 private String letter;
153 private String groupingSep;
154 private String groupingSize;
155 private bool forwardCompatibility;
157 internal override bool CompileAttribute(Compiler compiler) {
158 string name = compiler.Input.LocalName;
159 string value = compiler.Input.Value;
160 if (Ref.Equal(name, compiler.Atoms.Level)) {
161 if (value != "any" && value != "multiple" && value != "single") {
162 throw XsltException.Create(Res.Xslt_InvalidAttrValue, "level", value);
164 this.level = value;
166 else if (Ref.Equal(name, compiler.Atoms.Count)) {
167 this.countPattern = value;
168 this.countKey = compiler.AddQuery(value, /*allowVars:*/true, /*allowKey:*/true, /*pattern*/true);
170 else if (Ref.Equal(name, compiler.Atoms.From)) {
171 this.from = value;
172 this.fromKey = compiler.AddQuery(value, /*allowVars:*/true, /*allowKey:*/true, /*pattern*/true);
174 else if (Ref.Equal(name, compiler.Atoms.Value)) {
175 this.value = value;
176 this.valueKey = compiler.AddQuery(value);
178 else if (Ref.Equal(name, compiler.Atoms.Format)) {
179 this.formatAvt = Avt.CompileAvt(compiler, value);
181 else if (Ref.Equal(name, compiler.Atoms.Lang)) {
182 this.langAvt = Avt.CompileAvt(compiler, value);
184 else if (Ref.Equal(name, compiler.Atoms.LetterValue)) {
185 this.letterAvt = Avt.CompileAvt(compiler, value);
187 else if (Ref.Equal(name, compiler.Atoms.GroupingSeparator)) {
188 this.groupingSepAvt = Avt.CompileAvt(compiler, value);
190 else if (Ref.Equal(name, compiler.Atoms.GroupingSize)) {
191 this.groupingSizeAvt = Avt.CompileAvt(compiler, value);
193 else {
194 return false;
196 return true;
199 internal override void Compile(Compiler compiler) {
200 CompileAttributes(compiler);
201 CheckEmpty(compiler);
203 this.forwardCompatibility = compiler.ForwardCompatibility;
204 this.formatTokens = ParseFormat(PrecalculateAvt(ref this.formatAvt));
205 this.letter = ParseLetter(PrecalculateAvt(ref this.letterAvt));
206 this.lang = PrecalculateAvt(ref this.langAvt);
207 this.groupingSep = PrecalculateAvt(ref this.groupingSepAvt);
208 if (this.groupingSep != null && this.groupingSep.Length > 1) {
209 throw XsltException.Create(Res.Xslt_CharAttribute, "grouping-separator");
211 this.groupingSize = PrecalculateAvt(ref this.groupingSizeAvt);
214 private int numberAny(Processor processor, ActionFrame frame) {
215 int result = 0;
216 // Our current point will be our end point in this search
217 XPathNavigator endNode = frame.Node;
218 if(endNode.NodeType == XPathNodeType.Attribute || endNode.NodeType == XPathNodeType.Namespace) {
219 endNode = endNode.Clone();
220 endNode.MoveToParent();
222 XPathNavigator startNode = endNode.Clone();
224 if(this.fromKey != Compiler.InvalidQueryKey) {
225 bool hitFrom = false;
226 // First try to find start by traversing up. This gives the best candidate or we hit root
228 if(processor.Matches(startNode, this.fromKey)) {
229 hitFrom = true;
230 break;
232 }while(startNode.MoveToParent());
234 Debug.Assert(
235 processor.Matches(startNode, this.fromKey) || // we hit 'from' or
236 startNode.NodeType == XPathNodeType.Root // we are at root
239 // from this point (matched parent | root) create descendent quiery:
240 // we have to reset 'result' on each 'from' node, because this point can' be not last from point;
241 XPathNodeIterator sel = startNode.SelectDescendants(XPathNodeType.All, /*matchSelf:*/ true);
242 while (sel.MoveNext()) {
243 if(processor.Matches(sel.Current, this.fromKey)) {
244 hitFrom = true;
245 result = 0;
247 else if(MatchCountKey(processor, frame.Node, sel.Current)) {
248 result ++;
250 if(sel.Current.IsSamePosition(endNode)) {
251 break;
254 if(! hitFrom) {
255 result = 0;
258 else {
259 // without 'from' we startting from the root
260 startNode.MoveToRoot();
261 XPathNodeIterator sel = startNode.SelectDescendants(XPathNodeType.All, /*matchSelf:*/ true);
262 // and count root node by itself
263 while (sel.MoveNext()) {
264 if (MatchCountKey(processor, frame.Node, sel.Current)) {
265 result ++;
267 if (sel.Current.IsSamePosition(endNode)) {
268 break;
272 return result;
275 // check 'from' condition:
276 // if 'from' exist it has to be ancestor-or-self for the nav
277 private bool checkFrom(Processor processor, XPathNavigator nav) {
278 if(this.fromKey == Compiler.InvalidQueryKey) {
279 return true;
281 do {
282 if (processor.Matches(nav, this.fromKey)) {
283 return true;
285 }while (nav.MoveToParent());
286 return false;
289 private bool moveToCount(XPathNavigator nav, Processor processor, XPathNavigator contextNode) {
290 do {
291 if (this.fromKey != Compiler.InvalidQueryKey && processor.Matches(nav, this.fromKey)) {
292 return false;
294 if (MatchCountKey(processor, contextNode, nav)) {
295 return true;
297 }while (nav.MoveToParent());
298 return false;
301 private int numberCount(XPathNavigator nav, Processor processor, XPathNavigator contextNode) {
302 Debug.Assert(nav.NodeType != XPathNodeType.Attribute && nav.NodeType != XPathNodeType.Namespace);
303 Debug.Assert(MatchCountKey(processor, contextNode, nav));
304 XPathNavigator runner = nav.Clone();
305 int number = 1;
306 if (runner.MoveToParent()) {
307 runner.MoveToFirstChild();
308 while (! runner.IsSamePosition(nav)) {
309 if (MatchCountKey(processor, contextNode, runner)) {
310 number++;
312 if (! runner.MoveToNext()) {
313 Debug.Fail("We implementing preceding-sibling::node() and some how miss context node 'nav'");
314 break;
318 return number;
321 private static object SimplifyValue(object value) {
322 // If result of xsl:number is not in correct range it should be returned as is.
323 // so we need intermidiate string value.
324 // If it's already a double we would like to keep it as double.
325 // So this function converts to string only if if result is nodeset or RTF
326 Debug.Assert(!(value is int));
327 if (Type.GetTypeCode(value.GetType()) == TypeCode.Object) {
328 XPathNodeIterator nodeset = value as XPathNodeIterator;
329 if (nodeset != null) {
330 if (nodeset.MoveNext()) {
331 return nodeset.Current.Value;
333 return string.Empty;
335 XPathNavigator nav = value as XPathNavigator;
336 if (nav != null) {
337 return nav.Value;
340 return value;
343 internal override void Execute(Processor processor, ActionFrame frame) {
344 Debug.Assert(processor != null && frame != null);
345 ArrayList list = processor.NumberList;
346 switch (frame.State) {
347 case Initialized:
348 Debug.Assert(frame != null);
349 Debug.Assert(frame.NodeSet != null);
350 list.Clear();
351 if (this.valueKey != Compiler.InvalidQueryKey) {
352 list.Add(SimplifyValue(processor.Evaluate(frame, this.valueKey)));
354 else if (this.level == "any") {
355 int number = numberAny(processor, frame);
356 if (number != 0) {
357 list.Add(number);
360 else {
361 bool multiple = (this.level == "multiple");
362 XPathNavigator contextNode = frame.Node; // context of xsl:number element. We using this node in MatchCountKey()
363 XPathNavigator countNode = frame.Node.Clone(); // node we count for
364 if (countNode.NodeType == XPathNodeType.Attribute || countNode.NodeType == XPathNodeType.Namespace) {
365 countNode.MoveToParent();
367 while (moveToCount(countNode, processor, contextNode)) {
368 list.Insert(0, numberCount(countNode, processor, contextNode));
369 if(! multiple || ! countNode.MoveToParent()) {
370 break;
373 if(! checkFrom(processor, countNode)) {
374 list.Clear();
378 /*CalculatingFormat:*/
379 frame.StoredOutput = Format(list,
380 this.formatAvt == null ? this.formatTokens : ParseFormat(this.formatAvt.Evaluate(processor, frame)),
381 this.langAvt == null ? this.lang : this.langAvt .Evaluate(processor, frame),
382 this.letterAvt == null ? this.letter : ParseLetter(this.letterAvt.Evaluate(processor, frame)),
383 this.groupingSepAvt == null ? this.groupingSep : this.groupingSepAvt .Evaluate(processor, frame),
384 this.groupingSizeAvt == null ? this.groupingSize : this.groupingSizeAvt.Evaluate(processor, frame)
386 goto case OutputNumber;
387 case OutputNumber :
388 Debug.Assert(frame.StoredOutput != null);
389 if (! processor.TextEvent(frame.StoredOutput)) {
390 frame.State = OutputNumber;
391 break;
393 frame.Finished();
394 break;
395 default:
396 Debug.Fail("Invalid Number Action execution state");
397 break;
401 private bool MatchCountKey(Processor processor, XPathNavigator contextNode, XPathNavigator nav){
402 if (this.countKey != Compiler.InvalidQueryKey) {
403 return processor.Matches(nav, this.countKey);
405 if (contextNode.Name == nav.Name && BasicNodeType(contextNode.NodeType) == BasicNodeType(nav.NodeType)) {
406 return true;
408 return false;
411 private XPathNodeType BasicNodeType(XPathNodeType type) {
412 if(type == XPathNodeType.SignificantWhitespace || type == XPathNodeType.Whitespace) {
413 return XPathNodeType.Text;
415 else {
416 return type;
420 // Microsoft: perf.
421 // for each call to xsl:number Format() will build new NumberingFormat object.
422 // in case of no AVTs we can build this object at compile time and reuse it on execution time.
423 // even partial step in this d---- will be usefull (when cFormats == 0)
425 private static string Format(ArrayList numberlist, List<FormatInfo> tokens, string lang, string letter, string groupingSep, string groupingSize) {
426 StringBuilder result = new StringBuilder();
427 int cFormats = 0;
428 if (tokens != null) {
429 cFormats = tokens.Count;
432 NumberingFormat numberingFormat = new NumberingFormat();
433 if (groupingSize != null) {
434 try {
435 numberingFormat.setGroupingSize(Convert.ToInt32(groupingSize, CultureInfo.InvariantCulture));
437 catch (System.FormatException) {}
438 catch (System.OverflowException) {}
440 if (groupingSep != null) {
441 if (groupingSep.Length > 1) {
442 // It is a breaking change to throw an exception, SQLBUDT 324367
443 //throw XsltException.Create(Res.Xslt_CharAttribute, "grouping-separator");
445 numberingFormat.setGroupingSeparator(groupingSep);
447 if (0 < cFormats) {
448 FormatInfo prefix = tokens[0];
449 Debug.Assert(prefix == null || prefix.isSeparator);
450 FormatInfo sufix = null;
451 if (cFormats % 2 == 1) {
452 sufix = tokens[cFormats - 1];
453 cFormats --;
455 FormatInfo periodicSeparator = 2 < cFormats ? tokens[cFormats - 2] : DefaultSeparator;
456 FormatInfo periodicFormat = 0 < cFormats ? tokens[cFormats - 1] : DefaultFormat ;
457 if (prefix != null) {
458 result.Append(prefix.formatString);
460 int numberlistCount = numberlist.Count;
461 for(int i = 0; i < numberlistCount; i++ ) {
462 int formatIndex = i * 2;
463 bool haveFormat = formatIndex < cFormats;
464 if (0 < i) {
465 FormatInfo thisSeparator = haveFormat ? tokens[formatIndex + 0] : periodicSeparator;
466 Debug.Assert(thisSeparator.isSeparator);
467 result.Append(thisSeparator.formatString);
470 FormatInfo thisFormat = haveFormat ? tokens[formatIndex + 1] : periodicFormat;
471 Debug.Assert(!thisFormat.isSeparator);
473 //numberingFormat.setletter(this.letter);
474 //numberingFormat.setLang(this.lang);
476 numberingFormat.setNumberingType(thisFormat.numSequence);
477 numberingFormat.setMinLen(thisFormat.length);
478 result.Append(numberingFormat.FormatItem(numberlist[i]));
481 if (sufix != null) {
482 result.Append(sufix.formatString);
485 else {
486 numberingFormat.setNumberingType(NumberingSequence.Arabic);
487 for (int i = 0; i < numberlist.Count; i++) {
488 if (i != 0) {
489 result.Append(".");
491 result.Append(numberingFormat.FormatItem(numberlist[i]));
494 return result.ToString();
498 ----------------------------------------------------------------------------
499 mapFormatToken()
501 Maps a token of alphanumeric characters to a numbering format ID and a
502 minimum length bound. Tokens specify the character(s) that begins a
503 Unicode
504 numbering sequence. For example, "i" specifies lower case roman numeral
505 numbering. Leading "zeros" specify a minimum length to be maintained by
506 padding, if necessary.
507 ----------------------------------------------------------------------------
509 private static void mapFormatToken(String wsToken, int startLen, int tokLen, out NumberingSequence seq, out int pminlen) {
510 char wch = wsToken[startLen];
511 bool UseArabic = false;
512 pminlen = 1;
513 seq = NumberingSequence.Nil;
515 switch ((int)wch) {
516 case 0x0030: // Digit zero
517 case 0x0966: // Hindi digit zero
518 case 0x0e50: // Thai digit zero
519 case 0xc77b: // Korean digit zero
520 case 0xff10: // Digit zero (double-byte)
521 do {
522 // Leading zeros request padding. Track how much.
523 pminlen++;
524 } while ((--tokLen > 0) && (wch == wsToken[++startLen]));
526 if (wsToken[startLen] != (char)(wch + 1)) {
527 // If next character isn't "one", then use Arabic
528 UseArabic = true;
530 break;
533 if (!UseArabic) {
534 // Map characters of token to number format ID
535 switch ((int)wsToken[startLen]) {
536 case 0x0031: seq = NumberingSequence.Arabic; break;
537 case 0x0041: seq = NumberingSequence.UCLetter; break;
538 case 0x0049: seq = NumberingSequence.UCRoman; break;
539 case 0x0061: seq = NumberingSequence.LCLetter; break;
540 case 0x0069: seq = NumberingSequence.LCRoman; break;
541 case 0x0410: seq = NumberingSequence.UCRus; break;
542 case 0x0430: seq = NumberingSequence.LCRus; break;
543 case 0x05d0: seq = NumberingSequence.Hebrew; break;
544 case 0x0623: seq = NumberingSequence.ArabicScript; break;
545 case 0x0905: seq = NumberingSequence.Hindi2; break;
546 case 0x0915: seq = NumberingSequence.Hindi1; break;
547 case 0x0967: seq = NumberingSequence.Hindi3; break;
548 case 0x0e01: seq = NumberingSequence.Thai1; break;
549 case 0x0e51: seq = NumberingSequence.Thai2; break;
550 case 0x30a2: seq = NumberingSequence.DAiueo; break;
551 case 0x30a4: seq = NumberingSequence.DIroha; break;
552 case 0x3131: seq = NumberingSequence.DChosung; break;
553 case 0x4e00: seq = NumberingSequence.FEDecimal; break;
554 case 0x58f1: seq = NumberingSequence.DbNum3; break;
555 case 0x58f9: seq = NumberingSequence.ChnCmplx; break;
556 case 0x5b50: seq = NumberingSequence.Zodiac2; break;
557 case 0xac00: seq = NumberingSequence.Ganada; break;
558 case 0xc77c: seq = NumberingSequence.KorDbNum1; break;
559 case 0xd558: seq = NumberingSequence.KorDbNum3; break;
560 case 0xff11: seq = NumberingSequence.DArabic; break;
561 case 0xff71: seq = NumberingSequence.Aiueo; break;
562 case 0xff72: seq = NumberingSequence.Iroha; break;
564 case 0x7532:
565 if (tokLen > 1 && wsToken[startLen + 1] == 0x5b50) {
566 // 60-based Zodiak numbering begins with two characters
567 seq = NumberingSequence.Zodiac3;
568 tokLen--;
569 startLen++;
571 else {
572 // 10-based Zodiak numbering begins with one character
573 seq = NumberingSequence.Zodiac1;
575 break;
576 default:
577 seq = NumberingSequence.Arabic;
578 break;
582 //if (tokLen != 1 || UseArabic) {
583 if (UseArabic) {
584 // If remaining token length is not 1, then don't recognize
585 // sequence and default to Arabic with no zero padding.
586 seq = NumberingSequence.Arabic;
587 pminlen = 0;
593 ----------------------------------------------------------------------------
594 parseFormat()
596 Parse format string into format tokens (alphanumeric) and separators
597 (non-alphanumeric).
600 private static List<FormatInfo> ParseFormat(string formatString) {
601 if (formatString == null || formatString.Length == 0) {
602 return null;
604 int length = 0;
605 bool lastAlphaNumeric = CharUtil.IsAlphaNumeric(formatString[length]);
606 List<FormatInfo> tokens = new List<FormatInfo>();
607 int count = 0;
609 if (lastAlphaNumeric) {
610 // If the first one is alpha num add empty separator as a prefix.
611 tokens.Add(null);
614 while (length <= formatString.Length) {
615 // Loop until a switch from format token to separator is detected (or vice-versa)
616 bool currentchar = length < formatString.Length ? CharUtil.IsAlphaNumeric(formatString[length]) : !lastAlphaNumeric;
617 if (lastAlphaNumeric != currentchar) {
618 FormatInfo formatInfo = new FormatInfo();
619 if (lastAlphaNumeric) {
620 // We just finished a format token. Map it to a numbering format ID and a min-length bound.
621 mapFormatToken(formatString, count, length - count, out formatInfo.numSequence, out formatInfo.length);
623 else {
624 formatInfo.isSeparator = true;
625 // We just finished a separator. Save its length and a pointer to it.
626 formatInfo.formatString = formatString.Substring(count, length - count);
628 count = length;
629 length++;
630 // Begin parsing the next format token or separator
632 tokens.Add(formatInfo);
633 // Flip flag from format token to separator (or vice-versa)
634 lastAlphaNumeric = currentchar;
636 else {
637 length++;
641 return tokens;
644 private string ParseLetter(string letter) {
645 if (letter == null || letter == "traditional" || letter == "alphabetic") {
646 return letter;
648 if (! this.forwardCompatibility) {
649 throw XsltException.Create(Res.Xslt_InvalidAttrValue, "letter-value", letter);
651 return null;