1 //------------------------------------------------------------------------------
2 // <copyright file="XPathScanner.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace MS.Internal.Xml.XPath {
11 using System.Xml.XPath;
12 using System.Diagnostics;
13 using System.Globalization;
15 using System.Collections;
17 internal sealed class XPathScanner {
18 private string xpathExpr;
19 private int xpathExprIndex;
21 private char currentChar;
23 private string prefix;
24 private string stringValue;
25 private double numberValue = double.NaN;
26 private bool canBeFunction;
27 private XmlCharType xmlCharType = XmlCharType.Instance;
29 public XPathScanner(string xpathExpr) {
30 if (xpathExpr == null) {
31 throw XPathException.Create(Res.Xp_ExprExpected, string.Empty);
33 this.xpathExpr = xpathExpr;
38 public string SourceText { get { return this.xpathExpr; } }
40 private char CurerntChar { get { return currentChar; } }
42 private bool NextChar() {
43 Debug.Assert(0 <= xpathExprIndex && xpathExprIndex <= xpathExpr.Length);
44 if (xpathExprIndex < xpathExpr.Length) {
45 currentChar = xpathExpr[xpathExprIndex ++];
54 #if XML10_FIFTH_EDITION
55 private char PeekNextChar() {
56 Debug.Assert(0 <= xpathExprIndex && xpathExprIndex <= xpathExpr.Length);
57 if (xpathExprIndex < xpathExpr.Length) {
58 return xpathExpr[xpathExprIndex];
61 Debug.Assert(xpathExprIndex == xpathExpr.Length);
67 public LexKind Kind { get { return this.kind; } }
71 Debug.Assert(this.kind == LexKind.Name || this.kind == LexKind.Axe);
72 Debug.Assert(this.name != null);
77 public string Prefix {
79 Debug.Assert(this.kind == LexKind.Name);
80 Debug.Assert(this.prefix != null);
85 public string StringValue {
87 Debug.Assert(this.kind == LexKind.String);
88 Debug.Assert(this.stringValue != null);
89 return this.stringValue;
93 public double NumberValue {
95 Debug.Assert(this.kind == LexKind.Number);
96 Debug.Assert(this.numberValue != double.NaN);
97 return this.numberValue;
101 // To parse PathExpr we need a way to distinct name from function.
102 // THis distinction can't be done without context: "or (1 != 0)" this this a function or 'or' in OrExp
103 public bool CanBeFunction {
105 Debug.Assert(this.kind == LexKind.Name);
106 return this.canBeFunction;
111 while (xmlCharType.IsWhiteSpace(this.CurerntChar) && NextChar()) ;
114 public bool NextLex() {
116 switch (this.CurerntChar) {
120 case ',': case '@': case '(': case ')':
121 case '|': case '*': case '[': case ']':
122 case '+': case '-': case '=': case '#':
124 kind = (LexKind) Convert.ToInt32(this.CurerntChar, CultureInfo.InvariantCulture);
130 if (this.CurerntChar == '=') {
138 if (this.CurerntChar == '=') {
146 if (this.CurerntChar == '=') {
154 if (this.CurerntChar == '.') {
155 kind = LexKind.DotDot;
158 else if (XmlCharType.IsDigit(this.CurerntChar)) {
159 kind = LexKind.Number;
160 numberValue = ScanFraction();
164 kind = LexKind.Slash;
166 if (this.CurerntChar == '/') {
167 kind = LexKind.SlashSlash;
173 this.kind = LexKind.String;
174 this.stringValue = ScanString();
177 if (XmlCharType.IsDigit(this.CurerntChar)) {
178 kind = LexKind.Number;
179 numberValue = ScanNumber();
181 else if (xmlCharType.IsStartNCNameSingleChar(this.CurerntChar)
182 #if XML10_FIFTH_EDITION
183 || xmlCharType.IsNCNameHighSurrogateChar(this.CurerntChar)
187 this.name = ScanName();
188 this.prefix = string.Empty;
189 // "foo:bar" is one lexem not three because it doesn't allow spaces in between
190 // We should distinct it from "foo::" and need process "foo ::" as well
191 if (this.CurerntChar == ':') {
193 // can be "foo:bar" or "foo::"
194 if (this.CurerntChar == ':') { // "foo::"
198 else { // "foo:*", "foo:bar" or "foo: "
199 this.prefix = this.name;
200 if (this.CurerntChar == '*') {
204 else if (xmlCharType.IsStartNCNameSingleChar(this.CurerntChar)
205 #if XML10_FIFTH_EDITION
206 || xmlCharType.IsNCNameHighSurrogateChar(this.CurerntChar)
209 this.name = ScanName();
212 throw XPathException.Create(Res.Xp_InvalidName, SourceText);
219 if (this.CurerntChar == ':') {
221 // it can be "foo ::" or just "foo :"
222 if (this.CurerntChar == ':') {
227 throw XPathException.Create(Res.Xp_InvalidName, SourceText);
232 this.canBeFunction = (this.CurerntChar == '(');
235 throw XPathException.Create(Res.Xp_InvalidToken, SourceText);
242 private double ScanNumber() {
243 Debug.Assert(this.CurerntChar == '.' || XmlCharType.IsDigit(this.CurerntChar));
244 int start = xpathExprIndex - 1;
246 while (XmlCharType.IsDigit(this.CurerntChar)) {
249 if (this.CurerntChar == '.') {
251 while (XmlCharType.IsDigit(this.CurerntChar)) {
255 return XmlConvert.ToXPathDouble(this.xpathExpr.Substring(start, len));
258 private double ScanFraction() {
259 Debug.Assert(XmlCharType.IsDigit(this.CurerntChar));
260 int start = xpathExprIndex - 2;
261 Debug.Assert(0 <= start && this.xpathExpr[start] == '.');
263 while (XmlCharType.IsDigit(this.CurerntChar)) {
266 return XmlConvert.ToXPathDouble(this.xpathExpr.Substring(start, len));
269 private string ScanString() {
270 char endChar = this.CurerntChar;
272 int start = xpathExprIndex - 1;
274 while(this.CurerntChar != endChar) {
276 throw XPathException.Create(Res.Xp_UnclosedString);
280 Debug.Assert(this.CurerntChar == endChar);
282 return this.xpathExpr.Substring(start, len);
285 private string ScanName() {
286 Debug.Assert(xmlCharType.IsStartNCNameSingleChar(this.CurerntChar)
287 #if XML10_FIFTH_EDITION
288 || xmlCharType.IsNCNameHighSurrogateChar(this.CurerntChar)
291 int start = xpathExprIndex - 1;
295 if (xmlCharType.IsNCNameSingleChar(this.CurerntChar)) {
299 #if XML10_FIFTH_EDITION
300 else if (xmlCharType.IsNCNameSurrogateChar(this.PeekNextChar(), this.CurerntChar)) {
310 return this.xpathExpr.Substring(start, len);
313 public enum LexKind {
339 SlashSlash = 'S', // //
340 Name = 'n', // XML _Name
341 String = 's', // Quoted string constant
342 Number = 'd', // _Number constant
343 Axe = 'a', // Axe (like child::)