ValidateNames.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. //------------------------------------------------------------------------------
  2. // <copyright file="ValidateNames.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. //------------------------------------------------------------------------------
  7. using System;
  8. #if !SILVERLIGHT
  9. using System.Xml.XPath;
  10. #endif
  11. using System.Diagnostics;
  12. using System.Globalization;
  13. #if SILVERLIGHT_XPATH
  14. namespace System.Xml.XPath {
  15. #else
  16. namespace System.Xml {
  17. #endif
  18. /// <summary>
  19. /// Contains various static functions and methods for parsing and validating:
  20. /// NCName (not namespace-aware, no colons allowed)
  21. /// QName (prefix:local-name)
  22. /// </summary>
  23. internal static class ValidateNames {
  24. internal enum Flags {
  25. NCNames = 0x1, // Validate that each non-empty prefix and localName is a valid NCName
  26. CheckLocalName = 0x2, // Validate the local-name
  27. CheckPrefixMapping = 0x4, // Validate the prefix --> namespace mapping
  28. All = 0x7,
  29. AllExceptNCNames = 0x6,
  30. AllExceptPrefixMapping = 0x3,
  31. };
  32. static XmlCharType xmlCharType = XmlCharType.Instance;
  33. #if !SILVERLIGHT
  34. //-----------------------------------------------
  35. // Nmtoken parsing
  36. //-----------------------------------------------
  37. /// <summary>
  38. /// Attempts to parse the input string as an Nmtoken (see the XML spec production [7] && XML Namespaces spec).
  39. /// Quits parsing when an invalid Nmtoken char is reached or the end of string is reached.
  40. /// Returns the number of valid Nmtoken chars that were parsed.
  41. /// </summary>
  42. internal static unsafe int ParseNmtoken(string s, int offset) {
  43. Debug.Assert(s != null && offset <= s.Length);
  44. // Keep parsing until the end of string or an invalid NCName character is reached
  45. int i = offset;
  46. while (i < s.Length) {
  47. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCNameSC) != 0) { // if (xmlCharType.IsNCNameSingleChar(s[i])) {
  48. i++;
  49. }
  50. #if XML10_FIFTH_EDITION
  51. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  52. i += 2;
  53. }
  54. #endif
  55. else {
  56. break;
  57. }
  58. }
  59. return i - offset;
  60. }
  61. #endif
  62. //-----------------------------------------------
  63. // Nmtoken parsing (no XML namespaces support)
  64. //-----------------------------------------------
  65. /// <summary>
  66. /// Attempts to parse the input string as an Nmtoken (see the XML spec production [7]) without taking
  67. /// into account the XML Namespaces spec. What it means is that the ':' character is allowed at any
  68. /// position and any number of times in the token.
  69. /// Quits parsing when an invalid Nmtoken char is reached or the end of string is reached.
  70. /// Returns the number of valid Nmtoken chars that were parsed.
  71. /// </summary>
  72. #if SILVERLIGHT && !SILVERLIGHT_DISABLE_SECURITY && XMLCHARTYPE_USE_RESOURCE
  73. [System.Security.SecuritySafeCritical]
  74. #endif
  75. internal static unsafe int ParseNmtokenNoNamespaces(string s, int offset) {
  76. Debug.Assert(s != null && offset <= s.Length);
  77. // Keep parsing until the end of string or an invalid Name character is reached
  78. int i = offset;
  79. while (i < s.Length) {
  80. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCNameSC) != 0 || s[i] == ':') { // if (xmlCharType.IsNameSingleChar(s[i])) {
  81. i++;
  82. }
  83. #if XML10_FIFTH_EDITION
  84. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  85. i += 2;
  86. }
  87. #endif
  88. else {
  89. break;
  90. }
  91. }
  92. return i - offset;
  93. }
  94. // helper methods
  95. internal static bool IsNmtokenNoNamespaces(string s) {
  96. int endPos = ParseNmtokenNoNamespaces(s, 0);
  97. return endPos > 0 && endPos == s.Length;
  98. }
  99. //-----------------------------------------------
  100. // Name parsing (no XML namespaces support)
  101. //-----------------------------------------------
  102. /// <summary>
  103. /// Attempts to parse the input string as a Name without taking into account the XML Namespaces spec.
  104. /// What it means is that the ':' character does not delimiter prefix and local name, but it is a regular
  105. /// name character, which is allowed to appear at any position and any number of times in the name.
  106. /// Quits parsing when an invalid Name char is reached or the end of string is reached.
  107. /// Returns the number of valid Name chars that were parsed.
  108. /// </summary>
  109. #if SILVERLIGHT && !SILVERLIGHT_DISABLE_SECURITY && XMLCHARTYPE_USE_RESOURCE
  110. [System.Security.SecuritySafeCritical]
  111. #endif
  112. internal static unsafe int ParseNameNoNamespaces(string s, int offset) {
  113. Debug.Assert(s != null && offset <= s.Length);
  114. // Quit if the first character is not a valid NCName starting character
  115. int i = offset;
  116. if (i < s.Length) {
  117. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCStartNameSC) != 0 || s[i] == ':') { // xmlCharType.IsStartNCNameSingleChar(s[i])) {
  118. i++;
  119. }
  120. #if XML10_FIFTH_EDITION
  121. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  122. i += 2;
  123. }
  124. #endif
  125. else {
  126. return 0; // no valid StartNCName char
  127. }
  128. // Keep parsing until the end of string or an invalid NCName character is reached
  129. while (i < s.Length) {
  130. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCNameSC) != 0 || s[i] == ':') { // if (xmlCharType.IsNCNameSingleChar(s[i]))
  131. i++;
  132. }
  133. #if XML10_FIFTH_EDITION
  134. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  135. i += 2;
  136. }
  137. #endif
  138. else {
  139. break;
  140. }
  141. }
  142. }
  143. return i - offset;
  144. }
  145. // helper methods
  146. internal static bool IsNameNoNamespaces(string s) {
  147. int endPos = ParseNameNoNamespaces(s, 0);
  148. return endPos > 0 && endPos == s.Length;
  149. }
  150. //-----------------------------------------------
  151. // NCName parsing
  152. //-----------------------------------------------
  153. /// <summary>
  154. /// Attempts to parse the input string as an NCName (see the XML Namespace spec).
  155. /// Quits parsing when an invalid NCName char is reached or the end of string is reached.
  156. /// Returns the number of valid NCName chars that were parsed.
  157. /// </summary>
  158. #if SILVERLIGHT && !SILVERLIGHT_DISABLE_SECURITY && XMLCHARTYPE_USE_RESOURCE
  159. [System.Security.SecuritySafeCritical]
  160. #endif
  161. internal static unsafe int ParseNCName(string s, int offset) {
  162. Debug.Assert(s != null && offset <= s.Length);
  163. // Quit if the first character is not a valid NCName starting character
  164. int i = offset;
  165. if (i < s.Length) {
  166. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCStartNameSC) != 0) { // xmlCharType.IsStartNCNameSingleChar(s[i])) {
  167. i++;
  168. }
  169. #if XML10_FIFTH_EDITION
  170. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  171. i += 2;
  172. }
  173. #endif
  174. else {
  175. return 0; // no valid StartNCName char
  176. }
  177. // Keep parsing until the end of string or an invalid NCName character is reached
  178. while (i < s.Length) {
  179. if ((xmlCharType.charProperties[s[i]] & XmlCharType.fNCNameSC) != 0) { // if (xmlCharType.IsNCNameSingleChar(s[i]))
  180. i++;
  181. }
  182. #if XML10_FIFTH_EDITION
  183. else if (xmlCharType.IsNCNameSurrogateChar(s, i)) {
  184. i += 2;
  185. }
  186. #endif
  187. else {
  188. break;
  189. }
  190. }
  191. }
  192. return i - offset;
  193. }
  194. internal static int ParseNCName(string s) {
  195. return ParseNCName(s, 0);
  196. }
  197. /// <summary>
  198. /// Calls parseName and throws exception if the resulting name is not a valid NCName.
  199. /// Returns the input string if there is no error.
  200. /// </summary>
  201. internal static string ParseNCNameThrow(string s) {
  202. // throwOnError = true
  203. ParseNCNameInternal(s, true);
  204. return s;
  205. }
  206. /// <summary>
  207. /// Calls parseName and returns false or throws exception if the resulting name is not
  208. /// a valid NCName. Returns the input string if there is no error.
  209. /// </summary>
  210. private static bool ParseNCNameInternal(string s, bool throwOnError) {
  211. int len = ParseNCName(s, 0);
  212. if (len == 0 || len != s.Length) {
  213. // If the string is not a valid NCName, then throw or return false
  214. if (throwOnError) ThrowInvalidName(s, 0, len);
  215. return false;
  216. }
  217. return true;
  218. }
  219. //-----------------------------------------------
  220. // QName parsing
  221. //-----------------------------------------------
  222. /// <summary>
  223. /// Attempts to parse the input string as a QName (see the XML Namespace spec).
  224. /// Quits parsing when an invalid QName char is reached or the end of string is reached.
  225. /// Returns the number of valid QName chars that were parsed.
  226. /// Sets colonOffset to the offset of a colon character if it exists, or 0 otherwise.
  227. /// </summary>
  228. internal static int ParseQName(string s, int offset, out int colonOffset) {
  229. int len, lenLocal;
  230. // Assume no colon
  231. colonOffset = 0;
  232. // Parse NCName (may be prefix, may be local name)
  233. len = ParseNCName(s, offset);
  234. if (len != 0) {
  235. // Non-empty NCName, so look for colon if there are any characters left
  236. offset += len;
  237. if (offset < s.Length && s[offset] == ':') {
  238. // First NCName was prefix, so look for local name part
  239. lenLocal = ParseNCName(s, offset + 1);
  240. if (lenLocal != 0) {
  241. // Local name part found, so increase total QName length (add 1 for colon)
  242. colonOffset = offset;
  243. len += lenLocal + 1;
  244. }
  245. }
  246. }
  247. return len;
  248. }
  249. /// <summary>
  250. /// Calls parseQName and throws exception if the resulting name is not a valid QName.
  251. /// Returns the prefix and local name parts.
  252. /// </summary>
  253. internal static void ParseQNameThrow(string s, out string prefix, out string localName) {
  254. int colonOffset;
  255. int len = ParseQName(s, 0, out colonOffset);
  256. if (len == 0 || len != s.Length) {
  257. // If the string is not a valid QName, then throw
  258. ThrowInvalidName(s, 0, len);
  259. }
  260. if (colonOffset != 0) {
  261. prefix = s.Substring(0, colonOffset);
  262. localName = s.Substring(colonOffset + 1);
  263. }
  264. else {
  265. prefix = "";
  266. localName = s;
  267. }
  268. }
  269. #if !SILVERLIGHT
  270. /// <summary>
  271. /// Parses the input string as a NameTest (see the XPath spec), returning the prefix and
  272. /// local name parts. Throws an exception if the given string is not a valid NameTest.
  273. /// If the NameTest contains a star, null values for localName (case NCName':*'), or for
  274. /// both localName and prefix (case '*') are returned.
  275. /// </summary>
  276. internal static void ParseNameTestThrow(string s, out string prefix, out string localName) {
  277. int len, lenLocal, offset;
  278. if (s.Length != 0 && s[0] == '*') {
  279. // '*' as a NameTest
  280. prefix = localName = null;
  281. len = 1;
  282. }
  283. else {
  284. // Parse NCName (may be prefix, may be local name)
  285. len = ParseNCName(s, 0);
  286. if (len != 0) {
  287. // Non-empty NCName, so look for colon if there are any characters left
  288. localName = s.Substring(0, len);
  289. if (len < s.Length && s[len] == ':') {
  290. // First NCName was prefix, so look for local name part
  291. prefix = localName;
  292. offset = len + 1;
  293. if (offset < s.Length && s[offset] == '*') {
  294. // '*' as a local name part, add 2 to len for colon and star
  295. localName = null;
  296. len += 2;
  297. }
  298. else {
  299. lenLocal = ParseNCName(s, offset);
  300. if (lenLocal != 0) {
  301. // Local name part found, so increase total NameTest length
  302. localName = s.Substring(offset, lenLocal);
  303. len += lenLocal + 1;
  304. }
  305. }
  306. }
  307. else {
  308. prefix = string.Empty;
  309. }
  310. }
  311. else {
  312. // Make the compiler happy
  313. prefix = localName = null;
  314. }
  315. }
  316. if (len == 0 || len != s.Length) {
  317. // If the string is not a valid NameTest, then throw
  318. ThrowInvalidName(s, 0, len);
  319. }
  320. }
  321. #endif
  322. /// <summary>
  323. /// Throws an invalid name exception.
  324. /// </summary>
  325. /// <param name="s">String that was parsed.</param>
  326. /// <param name="offsetStartChar">Offset in string where parsing began.</param>
  327. /// <param name="offsetBadChar">Offset in string where parsing failed.</param>
  328. internal static void ThrowInvalidName(string s, int offsetStartChar, int offsetBadChar) {
  329. // If the name is empty, throw an exception
  330. if (offsetStartChar >= s.Length)
  331. #if !SILVERLIGHT_XPATH
  332. throw new XmlException(Res.Xml_EmptyName, string.Empty);
  333. #else
  334. throw new XmlException(Res.GetString(Res.Xml_EmptyName, string.Empty));
  335. #endif
  336. Debug.Assert(offsetBadChar < s.Length);
  337. if (xmlCharType.IsNCNameSingleChar(s[offsetBadChar]) && !XmlCharType.Instance.IsStartNCNameSingleChar(s[offsetBadChar])) {
  338. // The error character is a valid name character, but is not a valid start name character
  339. #if !SILVERLIGHT_XPATH
  340. throw new XmlException(Res.Xml_BadStartNameChar, XmlException.BuildCharExceptionArgs(s, offsetBadChar));
  341. #else
  342. throw new XmlException(Res.GetString(Res.Xml_BadStartNameChar, XmlExceptionHelper.BuildCharExceptionArgs(s, offsetBadChar)));
  343. #endif
  344. }
  345. else {
  346. // The error character is an invalid name character
  347. #if !SILVERLIGHT_XPATH
  348. throw new XmlException(Res.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(s, offsetBadChar));
  349. #else
  350. throw new XmlException(Res.GetString(Res.Xml_BadNameChar, XmlExceptionHelper.BuildCharExceptionArgs(s, offsetBadChar)));
  351. #endif
  352. }
  353. }
  354. #if !SILVERLIGHT
  355. internal static Exception GetInvalidNameException(string s, int offsetStartChar, int offsetBadChar) {
  356. // If the name is empty, throw an exception
  357. if (offsetStartChar >= s.Length)
  358. return new XmlException(Res.Xml_EmptyName, string.Empty);
  359. Debug.Assert(offsetBadChar < s.Length);
  360. if (xmlCharType.IsNCNameSingleChar(s[offsetBadChar]) && !xmlCharType.IsStartNCNameSingleChar(s[offsetBadChar])) {
  361. // The error character is a valid name character, but is not a valid start name character
  362. return new XmlException(Res.Xml_BadStartNameChar, XmlException.BuildCharExceptionArgs(s, offsetBadChar));
  363. }
  364. else {
  365. // The error character is an invalid name character
  366. return new XmlException(Res.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(s, offsetBadChar));
  367. }
  368. }
  369. /// <summary>
  370. /// Returns true if "prefix" starts with the characters 'x', 'm', 'l' (case-insensitive).
  371. /// </summary>
  372. internal static bool StartsWithXml(string s) {
  373. if (s.Length < 3)
  374. return false;
  375. if (s[0] != 'x' && s[0] != 'X')
  376. return false;
  377. if (s[1] != 'm' && s[1] != 'M')
  378. return false;
  379. if (s[2] != 'l' && s[2] != 'L')
  380. return false;
  381. return true;
  382. }
  383. /// <summary>
  384. /// Returns true if "s" is a namespace that is reserved by Xml 1.0 or Namespace 1.0.
  385. /// </summary>
  386. internal static bool IsReservedNamespace(string s) {
  387. return s.Equals(XmlReservedNs.NsXml) || s.Equals(XmlReservedNs.NsXmlNs);
  388. }
  389. /// <summary>
  390. /// Throw if the specified name parts are not valid according to the rules of "nodeKind". Check only rules that are
  391. /// specified by the Flags.
  392. /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  393. /// </summary>
  394. internal static void ValidateNameThrow(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags) {
  395. // throwOnError = true
  396. ValidateNameInternal(prefix, localName, ns, nodeKind, flags, true);
  397. }
  398. /// <summary>
  399. /// Return false if the specified name parts are not valid according to the rules of "nodeKind". Check only rules that are
  400. /// specified by the Flags.
  401. /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  402. /// </summary>
  403. internal static bool ValidateName(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags) {
  404. // throwOnError = false
  405. return ValidateNameInternal(prefix, localName, ns, nodeKind, flags, false);
  406. }
  407. /// <summary>
  408. /// Return false or throw if the specified name parts are not valid according to the rules of "nodeKind". Check only rules
  409. /// that are specified by the Flags.
  410. /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  411. /// </summary>
  412. private static bool ValidateNameInternal(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags, bool throwOnError) {
  413. Debug.Assert(prefix != null && localName != null && ns != null);
  414. if ((flags & Flags.NCNames) != 0) {
  415. // 1. Verify that each non-empty prefix and localName is a valid NCName
  416. if (prefix.Length != 0)
  417. if (!ParseNCNameInternal(prefix, throwOnError)) {
  418. return false;
  419. }
  420. if (localName.Length != 0)
  421. if (!ParseNCNameInternal(localName, throwOnError)) {
  422. return false;
  423. }
  424. }
  425. if ((flags & Flags.CheckLocalName) != 0) {
  426. // 2. Determine whether the local name is valid
  427. switch (nodeKind) {
  428. case XPathNodeType.Element:
  429. // Elements and attributes must have a non-empty local name
  430. if (localName.Length == 0) {
  431. if (throwOnError) throw new XmlException(Res.Xdom_Empty_LocalName, string.Empty);
  432. return false;
  433. }
  434. break;
  435. case XPathNodeType.Attribute:
  436. // Attribute local name cannot be "xmlns" if namespace is empty
  437. if (ns.Length == 0 && localName.Equals("xmlns")) {
  438. if (throwOnError) throw new XmlException(Res.XmlBadName, new string[] {nodeKind.ToString(), localName});
  439. return false;
  440. }
  441. goto case XPathNodeType.Element;
  442. case XPathNodeType.ProcessingInstruction:
  443. // PI's local-name must be non-empty and cannot be 'xml' (case-insensitive)
  444. if (localName.Length == 0 || (localName.Length == 3 && StartsWithXml(localName))) {
  445. if (throwOnError) throw new XmlException(Res.Xml_InvalidPIName, localName);
  446. return false;
  447. }
  448. break;
  449. default:
  450. // All other node types must have empty local-name
  451. if (localName.Length != 0) {
  452. if (throwOnError) throw new XmlException(Res.XmlNoNameAllowed, nodeKind.ToString());
  453. return false;
  454. }
  455. break;
  456. }
  457. }
  458. if ((flags & Flags.CheckPrefixMapping) != 0) {
  459. // 3. Determine whether the prefix is valid
  460. switch (nodeKind) {
  461. case XPathNodeType.Element:
  462. case XPathNodeType.Attribute:
  463. case XPathNodeType.Namespace:
  464. if (ns.Length == 0) {
  465. // If namespace is empty, then prefix must be empty
  466. if (prefix.Length != 0) {
  467. if (throwOnError) throw new XmlException(Res.Xml_PrefixForEmptyNs, string.Empty);
  468. return false;
  469. }
  470. }
  471. else {
  472. // Don't allow empty attribute prefix since namespace is non-empty
  473. if (prefix.Length == 0 && nodeKind == XPathNodeType.Attribute) {
  474. if (throwOnError) throw new XmlException(Res.XmlBadName, new string[] {nodeKind.ToString(), localName});
  475. return false;
  476. }
  477. if (prefix.Equals("xml")) {
  478. // xml prefix must be mapped to the xml namespace
  479. if (!ns.Equals(XmlReservedNs.NsXml)) {
  480. if (throwOnError) throw new XmlException(Res.Xml_XmlPrefix, string.Empty);
  481. return false;
  482. }
  483. }
  484. else if (prefix.Equals("xmlns")) {
  485. // Prefix may never be 'xmlns'
  486. if (throwOnError) throw new XmlException(Res.Xml_XmlnsPrefix, string.Empty);
  487. return false;
  488. }
  489. else if (IsReservedNamespace(ns)) {
  490. // Don't allow non-reserved prefixes to map to xml or xmlns namespaces
  491. if (throwOnError) throw new XmlException(Res.Xml_NamespaceDeclXmlXmlns, string.Empty);
  492. return false;
  493. }
  494. }
  495. break;
  496. case XPathNodeType.ProcessingInstruction:
  497. // PI's prefix and namespace must be empty
  498. if (prefix.Length != 0 || ns.Length != 0) {
  499. if (throwOnError) throw new XmlException(Res.Xml_InvalidPIName, CreateName(prefix, localName));
  500. return false;
  501. }
  502. break;
  503. default:
  504. // All other node types must have empty prefix and namespace
  505. if (prefix.Length != 0 || ns.Length != 0) {
  506. if (throwOnError) throw new XmlException(Res.XmlNoNameAllowed, nodeKind.ToString());
  507. return false;
  508. }
  509. break;
  510. }
  511. }
  512. return true;
  513. }
  514. /// <summary>
  515. /// Creates a colon-delimited qname from prefix and local name parts.
  516. /// </summary>
  517. private static string CreateName(string prefix, string localName) {
  518. return (prefix.Length != 0) ? prefix + ":" + localName : localName;
  519. }
  520. #endif
  521. #if !SILVERLIGHT || SILVERLIGHT_XPATH
  522. /// <summary>
  523. /// Split a QualifiedName into prefix and localname, w/o any checking.
  524. /// (Used for XmlReader/XPathNavigator MoveTo(name) methods)
  525. /// </summary>
  526. internal static void SplitQName(string name, out string prefix, out string lname) {
  527. int colonPos = name.IndexOf(':');
  528. if (-1 == colonPos) {
  529. prefix = string.Empty;
  530. lname = name;
  531. }
  532. else if (0 == colonPos || (name.Length-1) == colonPos) {
  533. #if !SILVERLIGHT_XPATH
  534. throw new ArgumentException(Res.GetString(Res.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(':', '\0')), "name");
  535. #else
  536. throw new ArgumentException(Res.GetString(Res.Xml_BadNameChar, XmlExceptionHelper.BuildCharExceptionArgs(':', '\0')), "name");
  537. #endif
  538. }
  539. else {
  540. prefix = name.Substring(0, colonPos);
  541. colonPos++; // move after colon
  542. lname = name.Substring(colonPos, name.Length - colonPos);
  543. }
  544. }
  545. #endif
  546. }
  547. #if SILVERLIGHT_XPATH
  548. internal class XmlExceptionHelper
  549. {
  550. internal static string[] BuildCharExceptionArgs(string data, int invCharIndex)
  551. {
  552. return BuildCharExceptionArgs(data[invCharIndex], invCharIndex + 1 < data.Length ? data[invCharIndex + 1] : '\0');
  553. }
  554. internal static string[] BuildCharExceptionArgs(char[] data, int invCharIndex)
  555. {
  556. return BuildCharExceptionArgs(data, data.Length, invCharIndex);
  557. }
  558. internal static string[] BuildCharExceptionArgs(char[] data, int length, int invCharIndex)
  559. {
  560. Debug.Assert(invCharIndex < data.Length);
  561. Debug.Assert(invCharIndex < length);
  562. Debug.Assert(length <= data.Length);
  563. return BuildCharExceptionArgs(data[invCharIndex], invCharIndex + 1 < length ? data[invCharIndex + 1] : '\0');
  564. }
  565. internal static string[] BuildCharExceptionArgs(char invChar, char nextChar)
  566. {
  567. string[] aStringList = new string[2];
  568. // for surrogate characters include both high and low char in the message so that a full character is displayed
  569. if (XmlCharType.IsHighSurrogate(invChar) && nextChar != 0)
  570. {
  571. int combinedChar = XmlCharType.CombineSurrogateChar(nextChar, invChar);
  572. aStringList[0] = new string(new char[] { invChar, nextChar });
  573. aStringList[1] = string.Format(CultureInfo.InvariantCulture, "0x{0:X2}", combinedChar);
  574. }
  575. else
  576. {
  577. // don't include 0 character in the string - in means eof-of-string in native code, where this may bubble up to
  578. if ((int)invChar == 0)
  579. {
  580. aStringList[0] = ".";
  581. }
  582. else
  583. {
  584. aStringList[0] = invChar.ToString(CultureInfo.InvariantCulture);
  585. }
  586. aStringList[1] = string.Format(CultureInfo.InvariantCulture, "0x{0:X2}", (int)invChar);
  587. }
  588. return aStringList;
  589. }
  590. }
  591. #endif
  592. }