The Labs \ Source Viewer \ SSCLI \ System.Xml \ Flags

  1. //------------------------------------------------------------------------------
  2. // <copyright file="ValidateNames.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System;
  16. using System.Xml.XPath;
  17. using System.Diagnostics;
  18. namespace System.Xml
  19. {
  20.    
  21.     /// <summary>
  22.     /// Contains various static functions and methods for parsing and validating:
  23.     /// NCName (not namespace-aware, no colons allowed)
  24.     /// QName (prefix:local-name)
  25.     /// </summary>
  26.     internal class ValidateNames
  27.     {
  28.         // Not creatable
  29.         private ValidateNames()
  30.         {
  31.         }
  32.        
  33.         public enum Flags
  34.         {
  35.             NCNames = 1,
  36.             // Validate that each non-empty prefix and localName is a valid NCName
  37.             CheckLocalName = 2,
  38.             // Validate the local-name
  39.             CheckPrefixMapping = 4,
  40.             // Validate the prefix --> namespace mapping
  41.             All = 7,
  42.             AllExceptNCNames = 6,
  43.             AllExceptPrefixMapping = 3
  44.         }
  45.        
  46.        
  47.         //-----------------------------------------------
  48.         // NCName parsing
  49.         //-----------------------------------------------
  50.        
  51.         /// <summary>
  52.         /// Attempts to parse the input string as an NCName (see the XML Namespace spec).
  53.         /// Quits parsing when an invalid NCName char is reached or the end of string is reached.
  54.         /// Returns the number of valid NCName chars that were parsed.
  55.         /// </summary>
  56.         unsafe public static int ParseNCName(string s, int offset)
  57.         {
  58.             int offsetStart = offset;
  59.             XmlCharType xmlCharType = XmlCharType.Instance;
  60.             Debug.Assert(s != null && offset <= s.Length);
  61.            
  62.             // Quit if the first character is not a valid NCName starting character
  63.             if (offset < s.Length && (xmlCharType.charProperties[s[offset]] & XmlCharType.fNCStartName) != 0) {
  64.                 // xmlCharType.IsStartNCNameChar(s[offset])) {
  65.                 // Keep parsing until the end of string or an invalid NCName character is reached
  66.                 for (offset++; offset < s.Length; offset++) {
  67.                     if ((xmlCharType.charProperties[s[offset]] & XmlCharType.fNCName) == 0)
  68.                         // if (!xmlCharType.IsNCNameChar(s[offset]))
  69.                         break;
  70.                 }
  71.             }
  72.            
  73.             return offset - offsetStart;
  74.         }
  75.        
  76.         /// <summary>
  77.         /// Calls parseName and throws exception if the resulting name is not a valid NCName.
  78.         /// Returns the input string if there is no error.
  79.         /// </summary>
  80.         public static string ParseNCNameThrow(string s)
  81.         {
  82.             // throwOnError = true
  83.             ParseNCNameInternal(s, true);
  84.             return s;
  85.         }
  86.        
  87.         /// <summary>
  88.         /// Calls parseName and returns false or throws exception if the resulting name is not
  89.         /// a valid NCName. Returns the input string if there is no error.
  90.         /// </summary>
  91.         private static bool ParseNCNameInternal(string s, bool throwOnError)
  92.         {
  93.             int len = ParseNCName(s, 0);
  94.            
  95.             if (len == 0 || len != s.Length) {
  96.                 // If the string is not a valid NCName, then throw or return false
  97.                 if (throwOnError)
  98.                     ThrowInvalidName(s, 0, len);
  99.                 return false;
  100.             }
  101.            
  102.             return true;
  103.         }
  104.        
  105.        
  106.         //-----------------------------------------------
  107.         // QName parsing
  108.         //-----------------------------------------------
  109.        
  110.         /// <summary>
  111.         /// Attempts to parse the input string as a QName (see the XML Namespace spec).
  112.         /// Quits parsing when an invalid QName char is reached or the end of string is reached.
  113.         /// Returns the number of valid QName chars that were parsed.
  114.         /// Sets colonOffset to the offset of a colon character if it exists, or 0 otherwise.
  115.         /// </summary>
  116.         public static int ParseQName(string s, int offset, out int colonOffset)
  117.         {
  118.             int len;
  119.             int lenLocal;
  120.            
  121.             // Assume no colon
  122.             colonOffset = 0;
  123.            
  124.             // Parse NCName (may be prefix, may be local name)
  125.             len = ParseNCName(s, offset);
  126.             if (len != 0) {
  127.                
  128.                 // Non-empty NCName, so look for colon if there are any characters left
  129.                 offset += len;
  130.                 if (offset < s.Length && s[offset] == ':') {
  131.                    
  132.                     // First NCName was prefix, so look for local name part
  133.                     lenLocal = ParseNCName(s, offset + 1);
  134.                     if (lenLocal != 0) {
  135.                         // Local name part found, so increase total QName length (add 1 for colon)
  136.                         colonOffset = offset;
  137.                         len += lenLocal + 1;
  138.                     }
  139.                 }
  140.             }
  141.            
  142.             return len;
  143.         }
  144.        
  145.         /// <summary>
  146.         /// Calls parseQName and throws exception if the resulting name is not a valid QName.
  147.         /// Returns the prefix and local name parts.
  148.         /// </summary>
  149.         public static void ParseQNameThrow(string s, out string prefix, out string localName)
  150.         {
  151.             int colonOffset;
  152.             int len = ParseQName(s, 0, out colonOffset);
  153.            
  154.             if (len == 0 || len != s.Length) {
  155.                 // If the string is not a valid QName, then throw
  156.                 ThrowInvalidName(s, 0, len);
  157.             }
  158.            
  159.             if (colonOffset != 0) {
  160.                 prefix = s.Substring(0, colonOffset);
  161.                 localName = s.Substring(colonOffset + 1);
  162.             }
  163.             else {
  164.                 prefix = "";
  165.                 localName = s;
  166.             }
  167.         }
  168.        
  169.         /// <summary>
  170.         /// Parses the input string as a NameTest (see the XPath spec), returning the prefix and
  171.         /// local name parts. Throws an exception if the given string is not a valid NameTest.
  172.         /// If the NameTest contains a star, null values for localName (case NCName':*'), or for
  173.         /// both localName and prefix (case '*') are returned.
  174.         /// </summary>
  175.         public static void ParseNameTestThrow(string s, out string prefix, out string localName)
  176.         {
  177.             int len;
  178.             int lenLocal;
  179.             int offset;
  180.            
  181.             if (s.Length != 0 && s[0] == '*') {
  182.                 // '*' as a NameTest
  183.                 prefix = localName = null;
  184.                 len = 1;
  185.             }
  186.             else {
  187.                 // Parse NCName (may be prefix, may be local name)
  188.                 len = ParseNCName(s, 0);
  189.                 if (len != 0) {
  190.                    
  191.                     // Non-empty NCName, so look for colon if there are any characters left
  192.                     localName = s.Substring(0, len);
  193.                     if (len < s.Length && s[len] == ':') {
  194.                        
  195.                         // First NCName was prefix, so look for local name part
  196.                         prefix = localName;
  197.                         offset = len + 1;
  198.                         if (offset < s.Length && s[offset] == '*') {
  199.                             // '*' as a local name part, add 2 to len for colon and star
  200.                             localName = null;
  201.                             len += 2;
  202.                         }
  203.                         else {
  204.                             lenLocal = ParseNCName(s, offset);
  205.                             if (lenLocal != 0) {
  206.                                 // Local name part found, so increase total NameTest length
  207.                                 localName = s.Substring(offset, lenLocal);
  208.                                 len += lenLocal + 1;
  209.                             }
  210.                         }
  211.                     }
  212.                     else {
  213.                         prefix = string.Empty;
  214.                     }
  215.                 }
  216.                 else {
  217.                     // Make the compiler happy
  218.                     prefix = localName = null;
  219.                 }
  220.             }
  221.            
  222.             if (len == 0 || len != s.Length) {
  223.                 // If the string is not a valid NameTest, then throw
  224.                 ThrowInvalidName(s, 0, len);
  225.             }
  226.         }
  227.        
  228.         /// <summary>
  229.         /// Throws an invalid name exception.
  230.         /// </summary>
  231.         /// <param name="s">String that was parsed.</param>
  232.         /// <param name="offsetStartChar">Offset in string where parsing began.</param>
  233.         /// <param name="offsetBadChar">Offset in string where parsing failed.</param>
  234.         public static void ThrowInvalidName(string s, int offsetStartChar, int offsetBadChar)
  235.         {
  236.             // If the name is empty, throw an exception
  237.             if (offsetStartChar >= s.Length)
  238.                 throw new XmlException(Res.Xml_EmptyName, string.Empty);
  239.            
  240.             Debug.Assert(offsetBadChar < s.Length);
  241.            
  242.             if (XmlCharType.Instance.IsNCNameChar(s[offsetBadChar]) && !XmlCharType.Instance.IsStartNCNameChar(s[offsetBadChar])) {
  243.                 // The error character is a valid name character, but is not a valid start name character
  244.                 throw new XmlException(Res.Xml_BadStartNameChar, XmlException.BuildCharExceptionStr(s[offsetBadChar]));
  245.             }
  246.             else {
  247.                 // The error character is an invalid name character
  248.                 throw new XmlException(Res.Xml_BadNameChar, XmlException.BuildCharExceptionStr(s[offsetBadChar]));
  249.             }
  250.         }
  251.        
  252.         public static Exception GetInvalidNameException(string s, int offsetStartChar, int offsetBadChar)
  253.         {
  254.             // If the name is empty, throw an exception
  255.             if (offsetStartChar >= s.Length)
  256.                 return new XmlException(Res.Xml_EmptyName, string.Empty);
  257.            
  258.             Debug.Assert(offsetBadChar < s.Length);
  259.            
  260.             if (XmlCharType.Instance.IsNCNameChar(s[offsetBadChar]) && !XmlCharType.Instance.IsStartNCNameChar(s[offsetBadChar])) {
  261.                 // The error character is a valid name character, but is not a valid start name character
  262.                 return new XmlException(Res.Xml_BadStartNameChar, XmlException.BuildCharExceptionStr(s[offsetBadChar]));
  263.             }
  264.             else {
  265.                 // The error character is an invalid name character
  266.                 return new XmlException(Res.Xml_BadNameChar, XmlException.BuildCharExceptionStr(s[offsetBadChar]));
  267.             }
  268.         }
  269.         /// <summary>
  270.         /// Returns true if "prefix" starts with the characters 'x', 'm', 'l' (case-insensitive).
  271.         /// </summary>
  272.         public static bool StartsWithXml(string s)
  273.         {
  274.             if (s.Length < 3)
  275.                 return false;
  276.            
  277.             if (s[0] != 'x' && s[0] != 'X')
  278.                 return false;
  279.            
  280.             if (s[1] != 'm' && s[1] != 'M')
  281.                 return false;
  282.            
  283.             if (s[2] != 'l' && s[2] != 'L')
  284.                 return false;
  285.            
  286.             return true;
  287.         }
  288.        
  289.         /// <summary>
  290.         /// Returns true if "s" is a namespace that is reserved by Xml 1.0 or Namespace 1.0.
  291.         /// </summary>
  292.         public static bool IsReservedNamespace(string s)
  293.         {
  294.             return s.Equals(XmlReservedNs.NsXml) || s.Equals(XmlReservedNs.NsXmlNs);
  295.         }
  296.        
  297.         /// <summary>
  298.         /// Throw if the specified name parts are not valid according to the rules of "nodeKind". Check only rules that are
  299.         /// specified by the Flags.
  300.         /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  301.         /// </summary>
  302.         public static void ValidateNameThrow(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags)
  303.         {
  304.             // throwOnError = true
  305.             ValidateNameInternal(prefix, localName, ns, nodeKind, flags, true);
  306.         }
  307.        
  308.         /// <summary>
  309.         /// Return false if the specified name parts are not valid according to the rules of "nodeKind". Check only rules that are
  310.         /// specified by the Flags.
  311.         /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  312.         /// </summary>
  313.         public static bool ValidateName(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags)
  314.         {
  315.             // throwOnError = false
  316.             return ValidateNameInternal(prefix, localName, ns, nodeKind, flags, false);
  317.         }
  318.        
  319.         /// <summary>
  320.         /// Return false or throw if the specified name parts are not valid according to the rules of "nodeKind". Check only rules
  321.         /// that are specified by the Flags.
  322.         /// NOTE: Namespaces should be passed using a prefix, ns pair. "localName" is always string.Empty.
  323.         /// </summary>
  324.         private static bool ValidateNameInternal(string prefix, string localName, string ns, XPathNodeType nodeKind, Flags flags, bool throwOnError)
  325.         {
  326.             Debug.Assert(prefix != null && localName != null && ns != null);
  327.            
  328.             if ((flags & Flags.NCNames) != 0) {
  329.                
  330.                 // 1. Verify that each non-empty prefix and localName is a valid NCName
  331.                 if (prefix.Length != 0)
  332.                     if (!ParseNCNameInternal(prefix, throwOnError)) {
  333.                         return false;
  334.                     }
  335.                
  336.                 if (localName.Length != 0)
  337.                     if (!ParseNCNameInternal(localName, throwOnError)) {
  338.                         return false;
  339.                     }
  340.             }
  341.            
  342.             if ((flags & Flags.CheckLocalName) != 0) {
  343.                
  344.                 // 2. Determine whether the local name is valid
  345.                 switch (nodeKind) {
  346.                     case XPathNodeType.Element:
  347.                         // Elements and attributes must have a non-empty local name
  348.                         if (localName.Length == 0) {
  349.                             if (throwOnError)
  350.                                 throw new XmlException(Res.Xdom_Empty_LocalName, string.Empty);
  351.                             return false;
  352.                         }
  353.                         break;
  354.                     case XPathNodeType.Attribute:
  355.                        
  356.                         // Attribute local name cannot be "xmlns" if namespace is empty
  357.                         if (ns.Length == 0 && localName.Equals("xmlns")) {
  358.                             if (throwOnError)
  359.                                 throw new XmlException(Res.XmlBadName, new string[] {nodeKind.ToString(), localName});
  360.                             return false;
  361.                         }
  362.                         goto case XPathNodeType.Element;
  363.                         break;
  364.                     case XPathNodeType.ProcessingInstruction:
  365.                        
  366.                         // PI's local-name must be non-empty and cannot be 'xml' (case-insensitive)
  367.                         if (localName.Length == 0 || (localName.Length == 3 && StartsWithXml(localName))) {
  368.                             if (throwOnError)
  369.                                 throw new XmlException(Res.Xml_InvalidPIName, localName);
  370.                             return false;
  371.                         }
  372.                         break;
  373.                     default:
  374.                        
  375.                         // All other node types must have empty local-name
  376.                         if (localName.Length != 0) {
  377.                             if (throwOnError)
  378.                                 throw new XmlException(Res.XmlNoNameAllowed, nodeKind.ToString());
  379.                             return false;
  380.                         }
  381.                         break;
  382.                 }
  383.             }
  384.            
  385.             if ((flags & Flags.CheckPrefixMapping) != 0) {
  386.                
  387.                 // 3. Determine whether the prefix is valid
  388.                 switch (nodeKind) {
  389.                     case XPathNodeType.Element:
  390.                     case XPathNodeType.Attribute:
  391.                     case XPathNodeType.Namespace:
  392.                         if (ns.Length == 0) {
  393.                             // If namespace is empty, then prefix must be empty
  394.                             if (prefix.Length != 0) {
  395.                                 if (throwOnError)
  396.                                     throw new XmlException(Res.Xml_PrefixForEmptyNs, string.Empty);
  397.                                 return false;
  398.                             }
  399.                         }
  400.                         else {
  401.                             // Don't allow empty attribute prefix since namespace is non-empty
  402.                             if (prefix.Length == 0 && nodeKind == XPathNodeType.Attribute) {
  403.                                 if (throwOnError)
  404.                                     throw new XmlException(Res.XmlBadName, new string[] {nodeKind.ToString(), localName});
  405.                                 return false;
  406.                             }
  407.                            
  408.                             if (prefix.Equals("xml")) {
  409.                                 // xml prefix must be mapped to the xml namespace
  410.                                 if (!ns.Equals(XmlReservedNs.NsXml)) {
  411.                                     if (throwOnError)
  412.                                         throw new XmlException(Res.Xml_XmlPrefix, string.Empty);
  413.                                     return false;
  414.                                 }
  415.                             }
  416.                             else if (prefix.Equals("xmlns")) {
  417.                                 // Prefix may never be 'xmlns'
  418.                                 if (throwOnError)
  419.                                     throw new XmlException(Res.Xml_XmlnsPrefix, string.Empty);
  420.                                 return false;
  421.                             }
  422.                             else if (IsReservedNamespace(ns)) {
  423.                                 // Don't allow non-reserved prefixes to map to xml or xmlns namespaces
  424.                                 if (throwOnError)
  425.                                     throw new XmlException(Res.Xml_NamespaceDeclXmlXmlns, string.Empty);
  426.                                 return false;
  427.                             }
  428.                         }
  429.                         break;
  430.                     case XPathNodeType.ProcessingInstruction:
  431.                        
  432.                         // PI's prefix and namespace must be empty
  433.                         if (prefix.Length != 0 || ns.Length != 0) {
  434.                             if (throwOnError)
  435.                                 throw new XmlException(Res.Xml_InvalidPIName, CreateName(prefix, localName));
  436.                             return false;
  437.                         }
  438.                         break;
  439.                     default:
  440.                        
  441.                         // All other node types must have empty prefix and namespace
  442.                         if (prefix.Length != 0 || ns.Length != 0) {
  443.                             if (throwOnError)
  444.                                 throw new XmlException(Res.XmlNoNameAllowed, nodeKind.ToString());
  445.                             return false;
  446.                         }
  447.                         break;
  448.                 }
  449.             }
  450.            
  451.             return true;
  452.         }
  453.        
  454.         /// <summary>
  455.         /// Creates a colon-delimited qname from prefix and local name parts.
  456.         /// </summary>
  457.         private static string CreateName(string prefix, string localName)
  458.         {
  459.             return (prefix.Length != 0) ? prefix + ":" + localName : localName;
  460.         }
  461.        
  462.         /// <summary>
  463.         /// Split a QualifiedName into prefix and localname, w/o any checking.
  464.         /// (Used for XmlReader/XPathNavigator MoveTo(name) methods)
  465.         /// </summary>
  466.         static internal void SplitQName(string name, out string prefix, out string lname)
  467.         {
  468.             int colonPos = name.IndexOf(':');
  469.             if (-1 == colonPos) {
  470.                 prefix = string.Empty;
  471.                 lname = name;
  472.             }
  473.             else if (0 == colonPos || (name.Length - 1) == colonPos) {
  474.                 throw new ArgumentException(Res.GetString(Res.Xml_BadNameChar, XmlException.BuildCharExceptionStr(':')), "name");
  475.             }
  476.             else {
  477.                 prefix = name.Substring(0, colonPos);
  478.                 colonPos++;
  479.                 // move after colon
  480.                 lname = name.Substring(colonPos, name.Length - colonPos);
  481.             }
  482.         }
  483.        
  484.     }
  485. }

Developer Fusion