XmlLanguage.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / System / Windows / Markup / XmlLanguage.cs / 1305600 / XmlLanguage.cs

                             
//------------------------------------------------------------------------
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation, 2005 
//
//  File:      XmlLanguage.cs 
// 
//  Contents:  A type that can be used for a property meant to hold the
//             a value expressed as xml:lang. 
//
//  Created:   11/22/2005 jerryd
//
//----------------------------------------------------------------------- 

using System; 
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Globalization;
using System.Text;
using System.IO;
using System.Windows; 
using MS.Internal.PresentationCore;
 
namespace System.Windows.Markup 
{
    ///  
    ///     An RFC3066 language tag for use in Xml markup.
    /// 
    /// 
    ///     The tag may or may not have a registered CultureInfo present on the system. 
    ///     This class is useful for dealing with values represented using xml:lang in XML.
    ///     Note that XML spec allows the empty string, although that is not permitted by RFC3066; 
    ///      therefore, this type permits "". 
    /// 
    [TypeConverter(typeof(XmlLanguageConverter))] 
    public class XmlLanguage
    {
        // There is a conscious choice here to use Hashtable rather than
        //   Dictionary. Dictionary offers no 
        //   concurrency guarantees whatsoever.  So if we used it, we would
        //   have to take one of two implementations approaches.  Either, we 
        //   would have to take a simple lock around every read and write 
        //   operation, causing needless thread-contention amongst threads
        //   doing read operations.  Or we have have to use a ReaderWriterLock, 
        //   which has measurable negative perf impact in simple real-world
        //   scenarios that don't have heavy thread contention.
        //
        // Hashtable is implemented so that as long as writers are protected 
        //   by a lock, readers do not need to take a lock.  This eliminates
        //   the thread contention problem of the first Dictionary 
        //   solutions.  And furthermore, it has measurable performance benefits 
        //   over both of the Dictionary solutions.
        private static Hashtable _cache = new Hashtable(InitialDictionarySize); 
        private const int InitialDictionarySize = 10;   // Three for "en-us", "en", and "", plus a few more

        private const int MaxCultureDepth = 32;
        private static XmlLanguage _empty = null; 

        private readonly string _lowerCaseTag; 
        private CultureInfo _equivalentCulture; 
        private CultureInfo _specificCulture;
        private CultureInfo _compatibleCulture; 
        private int _specificity;
        private bool _equivalentCultureFailed;  // only consult after checking _equivalentCulture == null

        ///  
        ///     PRIVATE constructor.  It is vital that this constructor be
        ///       called ONLY from the implementation of GetLanguage(). 
        ///       The implementation strategy depends upon reference-equality 
        ///       being a complete test for XmlLanguage-equality,
        ///       and GetLanguage's use of _cache is necessary to 
        ///       guarantee that reference-equality is sufficient.
        /// 
        private XmlLanguage(string lowercase)
        { 
            _lowerCaseTag = lowercase;
            _equivalentCulture = null; 
            _specificCulture = null; 
            _compatibleCulture = null;
            _specificity = -1; 
            _equivalentCultureFailed = false;
        }

        ///  
        ///     The XmlLanguage corresponding to string.Empty, whose EquivalentCulture is
        ///        CultureInfo.InvariantCulture. 
        ///  
        public static XmlLanguage Empty
        { 
            get
            {
                if (_empty == null)
                { 
                    // We MUST NOT call the private constructor, but instead call GetLanguage()!
                    _empty = GetLanguage(string.Empty); 
                } 
                return _empty;
            } 
        }

        /// 
        ///     Retrive an XmlLanguage for a given RFC 3066 language string. 
        /// 
        ///  
        ///     The language string may be empty, or else must conform to RFC 3066 rules: 
        ///     The first subtag must consist of only ASCII letters.
        ///     Additional subtags must consist of ASCII letters or numerals. 
        ///     Subtags are separated by a single hyphen character.
        ///     Every subtag must be 1 to 8 characters long.
        ///     No leading or trailing hyphens are permitted.
        ///  
        /// 
        ///     ietfLanguageTag parameter is null 
        ///  
        /// 
        ///     ietfLanguageTag is non-empty, but does not conform to the syntax specified in RFC 3066. 
        /// 
        public static XmlLanguage GetLanguage(string ietfLanguageTag)
        {
            XmlLanguage language; 

            if (ietfLanguageTag == null) 
                throw new ArgumentNullException("ietfLanguageTag"); 

            string lowercase = AsciiToLower(ietfLanguageTag);   // throws on non-ascii 

            language = (XmlLanguage) _cache[lowercase];
            if (language == null)
            { 
                ValidateLowerCaseTag(lowercase);            // throws on RFC 3066 validation failure
                lock (_cache.SyncRoot) 
                { 
                    // Double-check that it is still the case that language-tag is not
                    //  present in cache.  Without this double-check, there would 
                    //  be some risk that clients on two different threads might
                    //  get two different XmlLanguage instances for the same language
                    //  tag.
                    language = (XmlLanguage) _cache[lowercase]; 
                    if (language == null)
                    { 
                        _cache[lowercase] = language = new XmlLanguage(lowercase); 
                    }
                } 
            }

            return language;
         } 

 
 
       /// 
        ///     The RFC 3066 string. 
        /// 
        /// 
        ///     MAY return a normalized version of the originally-specified string.
        ///     MAY return the empty string. 
        /// 
        public string IetfLanguageTag 
        { 
            get
            { 
                return _lowerCaseTag;
            }
        }
 
        /// 
        ///     Returns IetfLanguageTag. 
        ///  
        public override string ToString()
        { 
            return IetfLanguageTag;
        }

 

        ///  
        ///     Returns a CultureInfo if and only if one is registered matching IetfLanguageTag 
        /// 
        ///  
        ///     There is no registered CultureInfo with given IetfLanguageTag.
        /// 
        public CultureInfo GetEquivalentCulture()
        { 
            if (_equivalentCulture == null)
            { 
                try 
                {
                    // Even if we previously failed to find an EquivalentCulture, we retry, if only to 
                    //   capture inner exception.
                    _equivalentCulture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(_lowerCaseTag);
                }
                catch (ArgumentException e) 
                {
                    _equivalentCultureFailed = true; 
                    throw new InvalidOperationException(SR.Get(SRID.XmlLangGetCultureFailure, _lowerCaseTag), e); 
                }
            } 

            return _equivalentCulture;
        }
 
        /// 
        ///     Finds the most-closely-related non-neutral registered CultureInfo, if one is available. 
        ///  
        /// 
        ///     A non-Neutral CultureInfo. 
        /// 
        /// 
        ///    There is no related non-Neutral CultureInfo registered.
        ///  
        /// 
        ///    Will return CultureInfo.InvariantCulture if-and-only-if this.Equals(XmlLanguage.Empty). 
        ///    Finds the registered CultureInfo matching the longest-possible prefix of this XmlLanguage. 
        ///       If that registered CultureInfo is Neutral, then relies on
        ///       CultureInfo.CreateSpecificCulture() to map from a Neutral CultureInfo to a Specific one. 
        /// 
        public CultureInfo GetSpecificCulture()
        {
            if (_specificCulture == null) 
            {
                if (_lowerCaseTag.Length == 0) 
                { 
                    _specificCulture = GetEquivalentCulture();
                } 
                else
                {
                    CultureInfo culture = GetCompatibleCulture();
 
                    if (culture.IetfLanguageTag.Length == 0)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.XmlLangGetSpecificCulture, _lowerCaseTag)); 
                    }
 
                    if (!culture.IsNeutralCulture)
                    {
                        _specificCulture = culture;
                    } 
                    else
                    { 
                        try 
                        {
                            // note that it's important that we use culture.Name, not culture.IetfLanguageTag, here 
                            culture = CultureInfo.CreateSpecificCulture(culture.Name);
                            _specificCulture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(culture.IetfLanguageTag);
                        }
                        catch (ArgumentException e) 
                        {
                            throw new InvalidOperationException(SR.Get(SRID.XmlLangGetSpecificCulture, _lowerCaseTag), e); 
                        } 
                    }
                } 
            }

            return _specificCulture;
        } 

        ///  
        ///     Finds a registered CultureInfo corresponding to the IetfLanguageTag, or the longest 
        ///       sequence of leading subtags for which we have a registered CultureInfo.
        ///  
        [FriendAccessAllowed]
        internal CultureInfo GetCompatibleCulture()
        {
            if (_compatibleCulture == null) 
            {
                CultureInfo culture = null; 
 
                if (!TryGetEquivalentCulture(out culture))
                { 
                    string languageTag = IetfLanguageTag;

                    do
                    { 
                        languageTag = Shorten(languageTag);
                        if (languageTag == null) 
                        { 
                            // Should never happen, because GetCultureinfoByIetfLanguageTag("") should
                            //  return InvariantCulture! 
                            culture =  CultureInfo.InvariantCulture;
                        }
                        else
                        { 
                            try
                            { 
                                culture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(languageTag); 
                            }
                            catch (ArgumentException) 
                            {
                            }
                        }
 
                    }
                    while (culture == null); 
                } 
                _compatibleCulture = culture;
            } 
            return _compatibleCulture;
        }

        ///  
        ///     Checks to see if a second XmlLanguage is included in range of languages specified
        ///       by this XmlLanguage. 
        ///  
        /// 
        ///    In addition to looking for prefix-matches in IetfLanguageTags, the implementation 
        ///      also considers the Parent relationships among registered CultureInfo's.  So, in
        ///      particular, this routine will report that "zh-hk" is in the range specified by
        ///      "zh-hant", even though the latter is not a prefix of the former. And because it
        ///      doesn't restrict itself to traversing CultureInfo.Parent, it will also report that 
        ///      "sr-latn-sp" is in the range covered by "sr-latn".  (Note that "sr-latn" does
        ///      does not have a registered CultureInfo.) 
        ///  
        [FriendAccessAllowed]
        internal bool RangeIncludes(XmlLanguage language) 
        {

            if (this.IsPrefixOf(language.IetfLanguageTag))
            { 
                return true;
            } 
 
            // We still need to do CultureInfo.Parent-aware processing, to cover, for example,
            //  the case that "zh-hant" includes "zh-hk". 
            return RangeIncludes(language.GetCompatibleCulture());
        }

        ///  
        ///     Checks to see if a CultureInfo is included in range of languages specified
        ///       by this XmlLanguage. 
        ///  
        /// 
        ///    In addition to looking for prefix-matches in IetfLanguageTags, the implementation 
        ///      also considers the Parent relationships among CultureInfo's.  So, in
        ///      particular, this routine will report that "zh-hk" is in the range specified by
        ///      "zh-hant", even though the latter is not a prefix of the former.   And because it
        ///      doesn't restrict itself to traversing CultureInfo.Parent, it will also report that 
        ///      "sr-latn-sp" is in the range covered by "sr-latn".  (Note that "sr-latn" does
        ///      does not have a registered CultureInfo.) 
        ///  
        internal bool RangeIncludes(CultureInfo culture)
        { 
            if (culture == null)
            {
                throw new ArgumentNullException("culture");
            } 

            // no need for special cases for InvariantCulture, which has IetfLanguageTag == "" 
 
            // Limit how far we'll walk up the hierarchy to avoid security threat.
            // We could check for cycles (e.g., culture.Parent.Parent == culture) 
            // but in in the case of non-malicious code there should be no cycles,
            // whereas in malicious code, checking for cycles doesn't mitigate the
            // threat; one could always implement Parent such that it always returns
            // a new CultureInfo for which Equals always returns false. 
            for (int i = 0; i < MaxCultureDepth; ++i)
            { 
                // Note that we don't actually insist on a there being CultureInfo corresponding 
                //  to familyMapLanguage.
                // The use of language.StartsWith() catches, for example,the case 
                //  where this="sr-latn", and culture.IetfLanguageTag=="sr-latn-sp".
                // In such a case, culture.Parent.IetfLanguageTag=="sr".
                //  (There is no registered CultureInfo with IetfLanguageTag=="sr-latn".)
                if (this.IsPrefixOf(culture.IetfLanguageTag)) 
                {
                    return true; 
                } 

                CultureInfo parentCulture = culture.Parent; 

                if (parentCulture == null
                        || parentCulture.Equals(CultureInfo.InvariantCulture)
                        || parentCulture == culture) 
                    break;
 
                culture = parentCulture; 
            }
 
            return false;
        }

        ///  
        ///     Compute a measure of specificity of the XmlLanguage, considering both
        ///       subtag length and the CultureInfo.Parent hierarchy. 
        ///  
        internal int GetSpecificity()
        { 
            if (_specificity < 0)
            {
                CultureInfo compatibleCulture = GetCompatibleCulture();
 
                int specificity = GetSpecificity(compatibleCulture, MaxCultureDepth);
 
                if (compatibleCulture != _equivalentCulture) 
                {
                    specificity += GetSubtagCount(_lowerCaseTag) - GetSubtagCount(compatibleCulture.IetfLanguageTag); 
                }

                _specificity = specificity;
            } 

            return _specificity; 
        } 

        ///  
        ///     Helper function for instance-method GetSpecificity.
        /// 
        /// 
        ///     To avoid a security threat, caller provides limit on how far we'll 
        ///       walk up the CultureInfo hierarchy.
        ///       We could check for cycles (e.g., culture.Parent.Parent == culture) 
        ///       but in in the case of non-malicious code there should be no cycles, 
        ///       whereas in malicious code, checking for cycles doesn't mitigate the
        ///       threat; one could always implement Parent such that it always returns 
        ///       a new CultureInfo for which Equals always returns false.
        /// 
        private static int GetSpecificity(CultureInfo culture, int maxDepth)
        { 
            int specificity = 0;
 
            if (maxDepth != 0 && culture != null) 
            {
                string languageTag = culture.IetfLanguageTag; 

                if (languageTag.Length > 0)
                {
                    specificity = Math.Max(GetSubtagCount(languageTag), 1 + GetSpecificity(culture.Parent, maxDepth - 1)); 
                }
            } 
 
            return specificity;
        } 

        private static int GetSubtagCount(string languageTag)
        {
            int tagLength = languageTag.Length; 
            int subtagCount = 0;
 
            if (tagLength > 0) 
            {
                subtagCount = 1; 

                for (int i = 0; i < tagLength; i++)
                {
                    if (languageTag[i] == '-') 
                    {
                        subtagCount += 1; 
                    } 

                } 
            }

            return subtagCount;
        } 

        internal MatchingLanguageCollection MatchingLanguages 
        { 
            get
            { 
                return new MatchingLanguageCollection(this);
            }
        }
 
        // collection of matching languages, ordered from most specific to least specific, starting
        //  with the start language and ending with invariant language ("") 
        internal struct MatchingLanguageCollection : IEnumerable, IEnumerable 
        {
            private XmlLanguage _start; 
            public MatchingLanguageCollection(XmlLanguage start)
            {
                _start = start;
            } 

 
            // strongly typed, avoids boxing 
            public MatchingLanguageEnumerator GetEnumerator()
            { 
                return new MatchingLanguageEnumerator(_start);
            }

            // strongly typed, boxes 
            IEnumerator IEnumerable.GetEnumerator()
            { 
                return GetEnumerator(); 
            }
 
            // weakly typed, boxed
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator(); 
            }
        } 
 
        internal struct MatchingLanguageEnumerator : IEnumerator, IEnumerator
        { 
            private readonly XmlLanguage _start;
            private XmlLanguage _current;
            private bool _atStart;
            private bool _pastEnd; 
            private int _maxCultureDepth;
 
            public MatchingLanguageEnumerator(XmlLanguage start) 
            {
                _start = start; 
                _current = start;
                _pastEnd = false;
                _atStart = true;
                _maxCultureDepth = XmlLanguage.MaxCultureDepth; 
            }
 
            public void Reset() 
            {
                _current = _start; 
                _pastEnd = false;
                _atStart = true;
                _maxCultureDepth = XmlLanguage.MaxCultureDepth;
            } 

            public XmlLanguage Current 
            { 
                get
                { 
                    if (_atStart)
                    {
                        throw new InvalidOperationException(SR.Get(SRID.Enumerator_NotStarted));
                    } 
                    if (_pastEnd)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.Enumerator_ReachedEnd)); 
                    }
 
                    return _current;
                }
            }
 
            public bool MoveNext()
            { 
                if (_atStart) 
                {
                    _atStart = false; 
                    return true;
                }
                else if (_current.IetfLanguageTag.Length == 0)
                { 
                    _atStart = false;
                    _pastEnd = true; 
                    return false; 
                }
                else 
                {
                    XmlLanguage prefixLanguage = _current.PrefixLanguage;
                    CultureInfo culture = null;
 
                    if (_maxCultureDepth > 0)
                    { 
                        if (_current.TryGetEquivalentCulture(out culture)) 
                        {
                            culture = culture.Parent; 
                        }
                        else
                        {
                            culture = null; 
                        }
                    } 
 
                    if (culture == null)
                    { 
                        _current = prefixLanguage;
                        _atStart = false;
                        return true;
                    } 
                    else
                    { 
                        // We MUST NOT call the private constructor, but instead call GetLanguage()! 
                        XmlLanguage parentLanguage = XmlLanguage.GetLanguage(culture.IetfLanguageTag);
 
                        if (parentLanguage.IsPrefixOf(prefixLanguage.IetfLanguageTag))
                        {
                            _current = prefixLanguage;
                            _atStart = false; 
                            return true;
                        } 
                        else 
                        {
                            // We definitely do this if 
                            //   prefixLanguage.IsPrefixOf(parentLanguage.IetfLanguageTag)
                            // But if this is not true, then we are faced with a problem with
                            //   divergent paths between prefix-tags and parent-CultureInfos.
                            //   This code makes the arbitrary decision to follow the parent 
                            //   path when faced with this divergence.
                            // 
 

 

                            //
                            _maxCultureDepth -= 1;
                            _current = parentLanguage; 
                            _atStart = false;
                            return true; 
                        } 
                    }
                } 
            }

            object IEnumerator.Current
            { 
                get
                { 
                    return Current; 
                }
            } 

            void IDisposable.Dispose() { }

        } 

 
 
        /// 
        ///     Differs from calling string.StartsWith, because each subtag must match 
        ///         in its entirety.
        ///     Note that this routine returns true if the tags match.
        /// 
        private bool IsPrefixOf(string longTag) 
        {
            string prefix = IetfLanguageTag; 
 

            // if we fail a simple string-prefix test, we know we don't have a subtag-prefix. 
            if(!longTag.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            } 

            // string-prefix test passed -- now determine if we're at a subtag-boundary 
            return (prefix.Length == 0 || prefix.Length == longTag.Length || longTag[prefix.Length] == '-'); 
        }
 
        private bool TryGetEquivalentCulture(out CultureInfo culture)
        {
            culture = null;
 
            if (_equivalentCulture == null && !_equivalentCultureFailed)
            { 
                try 
                {
                    GetEquivalentCulture(); 
                }
                catch (InvalidOperationException)
                {
                } 
            }
 
            culture = _equivalentCulture; 

            return (culture != null); 
        }

        private XmlLanguage PrefixLanguage
        { 
            get
            { 
                string prefix = Shorten(IetfLanguageTag);   // can return null 

                // We MUST NOT call the private constructor, but instead call GetLanguage()! 
                return XmlLanguage.GetLanguage(prefix);     // throws on null
            }
        }
 
        /// 
        ///     Shorten a well-formed RFC 3066 string by one subtag. 
        ///  
        /// 
        ///     Shortens "" into null. 
        /// 
        private static string Shorten(string languageTag)
        {
            if (languageTag.Length == 0) 
            {
                return null; 
            } 

            int i = languageTag.Length - 1; 

            while (languageTag[i] != '-' && i > 0)
            {
                i -= 1; 
            }
 
            // i now contains of index of first character to be omitted from smaller tag 
            return languageTag.Substring (0, i);
        } 



 
        /// 
        ///     Throws an ArgumentException (or ArgumentNullException) is not the empty 
        ///       string, and does not conform to RFC 3066. 
        /// 
        ///  
        ///     It is assumed that caller has already converted to lower-case.
        ///     The language string may be empty, or else must conform to RFC 3066 rules:
        ///     The first subtag must consist of only ASCII letters.
        ///     Additional subtags must consist ASCII letters or numerals. 
        ///     Subtags are separated by a single hyphen character.
        ///     Every subtag must be 1 to 8 characters long. 
        ///     No leading or trailing hyphens are permitted. 
        /// 
        ///  
        /// tag is NULL.
        /// tag is non-empty, but does not conform to RFC 3066.
        private static void ValidateLowerCaseTag(string ietfLanguageTag)
        { 
            if (ietfLanguageTag == null)
            { 
                throw new ArgumentNullException("ietfLanguageTag"); 
            }
 
            if (ietfLanguageTag.Length > 0)
            {
                using (StringReader reader = new StringReader(ietfLanguageTag))
                { 
                    int i;
 
                    i = ParseSubtag(ietfLanguageTag, reader, /* isPrimary */ true); 
                    while (i != -1)
                    { 
                        i = ParseSubtag(ietfLanguageTag, reader, /* isPrimary */ false);
                    }
                }
            } 
        }
 
        // returns the character which terminated the subtag -- either '-' or -1 for 
        //  end of string.
        // throws exception on improper formatting 
        // It is assumed that caller has already converted to lower-case.
        static private int ParseSubtag(string ietfLanguageTag, StringReader reader, bool isPrimary)
        {
            int c; 
            bool ok;
            const int maxCharsPerSubtag = 8; 
 
            c = reader.Read();
 
            ok = IsLowerAlpha(c);
            if (!ok && !isPrimary)
                ok = IsDigit(c);
 
            if (!ok)
            { 
                ThrowParseException(ietfLanguageTag); 
            }
 
            int charsRead = 1;
            for (;;)
            {
                c = reader.Read(); 
                charsRead++;
 
                ok = IsLowerAlpha(c); 
                if (!ok && !isPrimary)
                { 
                    ok = IsDigit(c);
                }

                if (!ok) 
                {
                    if (c == -1 || c == '-') 
                    { 
                        return c;
                    } 
                    else
                    {
                        ThrowParseException(ietfLanguageTag);
                    } 
                }
                else 
                { 
                    if (charsRead > maxCharsPerSubtag)
                    { 
                        ThrowParseException(ietfLanguageTag);
                    }
                }
            } 
        }
 
        static private bool IsLowerAlpha(int c) 
        {
            return (c >= 'a' && c <= 'z'); 
        }

        static private bool IsDigit(int c)
        { 
            return c >= '0' && c <= '9';
        } 
 
        static private void ThrowParseException(string ietfLanguageTag)
        { 
             throw new ArgumentException(SR.Get(SRID.XmlLangMalformed, ietfLanguageTag), "ietfLanguageTag");
        }

        // throws if there is a non-7-bit ascii character 
        static private string AsciiToLower(string tag)
        { 
            int length = tag.Length; 

            for (int i = 0; i < length; i++) 
            {
                if (tag[i] > 127)
                {
                    ThrowParseException(tag); 
                }
            } 
 
            return tag.ToLowerInvariant();
        } 

    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
 
//------------------------------------------------------------------------
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation, 2005 
//
//  File:      XmlLanguage.cs 
// 
//  Contents:  A type that can be used for a property meant to hold the
//             a value expressed as xml:lang. 
//
//  Created:   11/22/2005 jerryd
//
//----------------------------------------------------------------------- 

using System; 
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Globalization;
using System.Text;
using System.IO;
using System.Windows; 
using MS.Internal.PresentationCore;
 
namespace System.Windows.Markup 
{
    ///  
    ///     An RFC3066 language tag for use in Xml markup.
    /// 
    /// 
    ///     The tag may or may not have a registered CultureInfo present on the system. 
    ///     This class is useful for dealing with values represented using xml:lang in XML.
    ///     Note that XML spec allows the empty string, although that is not permitted by RFC3066; 
    ///      therefore, this type permits "". 
    /// 
    [TypeConverter(typeof(XmlLanguageConverter))] 
    public class XmlLanguage
    {
        // There is a conscious choice here to use Hashtable rather than
        //   Dictionary. Dictionary offers no 
        //   concurrency guarantees whatsoever.  So if we used it, we would
        //   have to take one of two implementations approaches.  Either, we 
        //   would have to take a simple lock around every read and write 
        //   operation, causing needless thread-contention amongst threads
        //   doing read operations.  Or we have have to use a ReaderWriterLock, 
        //   which has measurable negative perf impact in simple real-world
        //   scenarios that don't have heavy thread contention.
        //
        // Hashtable is implemented so that as long as writers are protected 
        //   by a lock, readers do not need to take a lock.  This eliminates
        //   the thread contention problem of the first Dictionary 
        //   solutions.  And furthermore, it has measurable performance benefits 
        //   over both of the Dictionary solutions.
        private static Hashtable _cache = new Hashtable(InitialDictionarySize); 
        private const int InitialDictionarySize = 10;   // Three for "en-us", "en", and "", plus a few more

        private const int MaxCultureDepth = 32;
        private static XmlLanguage _empty = null; 

        private readonly string _lowerCaseTag; 
        private CultureInfo _equivalentCulture; 
        private CultureInfo _specificCulture;
        private CultureInfo _compatibleCulture; 
        private int _specificity;
        private bool _equivalentCultureFailed;  // only consult after checking _equivalentCulture == null

        ///  
        ///     PRIVATE constructor.  It is vital that this constructor be
        ///       called ONLY from the implementation of GetLanguage(). 
        ///       The implementation strategy depends upon reference-equality 
        ///       being a complete test for XmlLanguage-equality,
        ///       and GetLanguage's use of _cache is necessary to 
        ///       guarantee that reference-equality is sufficient.
        /// 
        private XmlLanguage(string lowercase)
        { 
            _lowerCaseTag = lowercase;
            _equivalentCulture = null; 
            _specificCulture = null; 
            _compatibleCulture = null;
            _specificity = -1; 
            _equivalentCultureFailed = false;
        }

        ///  
        ///     The XmlLanguage corresponding to string.Empty, whose EquivalentCulture is
        ///        CultureInfo.InvariantCulture. 
        ///  
        public static XmlLanguage Empty
        { 
            get
            {
                if (_empty == null)
                { 
                    // We MUST NOT call the private constructor, but instead call GetLanguage()!
                    _empty = GetLanguage(string.Empty); 
                } 
                return _empty;
            } 
        }

        /// 
        ///     Retrive an XmlLanguage for a given RFC 3066 language string. 
        /// 
        ///  
        ///     The language string may be empty, or else must conform to RFC 3066 rules: 
        ///     The first subtag must consist of only ASCII letters.
        ///     Additional subtags must consist of ASCII letters or numerals. 
        ///     Subtags are separated by a single hyphen character.
        ///     Every subtag must be 1 to 8 characters long.
        ///     No leading or trailing hyphens are permitted.
        ///  
        /// 
        ///     ietfLanguageTag parameter is null 
        ///  
        /// 
        ///     ietfLanguageTag is non-empty, but does not conform to the syntax specified in RFC 3066. 
        /// 
        public static XmlLanguage GetLanguage(string ietfLanguageTag)
        {
            XmlLanguage language; 

            if (ietfLanguageTag == null) 
                throw new ArgumentNullException("ietfLanguageTag"); 

            string lowercase = AsciiToLower(ietfLanguageTag);   // throws on non-ascii 

            language = (XmlLanguage) _cache[lowercase];
            if (language == null)
            { 
                ValidateLowerCaseTag(lowercase);            // throws on RFC 3066 validation failure
                lock (_cache.SyncRoot) 
                { 
                    // Double-check that it is still the case that language-tag is not
                    //  present in cache.  Without this double-check, there would 
                    //  be some risk that clients on two different threads might
                    //  get two different XmlLanguage instances for the same language
                    //  tag.
                    language = (XmlLanguage) _cache[lowercase]; 
                    if (language == null)
                    { 
                        _cache[lowercase] = language = new XmlLanguage(lowercase); 
                    }
                } 
            }

            return language;
         } 

 
 
       /// 
        ///     The RFC 3066 string. 
        /// 
        /// 
        ///     MAY return a normalized version of the originally-specified string.
        ///     MAY return the empty string. 
        /// 
        public string IetfLanguageTag 
        { 
            get
            { 
                return _lowerCaseTag;
            }
        }
 
        /// 
        ///     Returns IetfLanguageTag. 
        ///  
        public override string ToString()
        { 
            return IetfLanguageTag;
        }

 

        ///  
        ///     Returns a CultureInfo if and only if one is registered matching IetfLanguageTag 
        /// 
        ///  
        ///     There is no registered CultureInfo with given IetfLanguageTag.
        /// 
        public CultureInfo GetEquivalentCulture()
        { 
            if (_equivalentCulture == null)
            { 
                try 
                {
                    // Even if we previously failed to find an EquivalentCulture, we retry, if only to 
                    //   capture inner exception.
                    _equivalentCulture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(_lowerCaseTag);
                }
                catch (ArgumentException e) 
                {
                    _equivalentCultureFailed = true; 
                    throw new InvalidOperationException(SR.Get(SRID.XmlLangGetCultureFailure, _lowerCaseTag), e); 
                }
            } 

            return _equivalentCulture;
        }
 
        /// 
        ///     Finds the most-closely-related non-neutral registered CultureInfo, if one is available. 
        ///  
        /// 
        ///     A non-Neutral CultureInfo. 
        /// 
        /// 
        ///    There is no related non-Neutral CultureInfo registered.
        ///  
        /// 
        ///    Will return CultureInfo.InvariantCulture if-and-only-if this.Equals(XmlLanguage.Empty). 
        ///    Finds the registered CultureInfo matching the longest-possible prefix of this XmlLanguage. 
        ///       If that registered CultureInfo is Neutral, then relies on
        ///       CultureInfo.CreateSpecificCulture() to map from a Neutral CultureInfo to a Specific one. 
        /// 
        public CultureInfo GetSpecificCulture()
        {
            if (_specificCulture == null) 
            {
                if (_lowerCaseTag.Length == 0) 
                { 
                    _specificCulture = GetEquivalentCulture();
                } 
                else
                {
                    CultureInfo culture = GetCompatibleCulture();
 
                    if (culture.IetfLanguageTag.Length == 0)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.XmlLangGetSpecificCulture, _lowerCaseTag)); 
                    }
 
                    if (!culture.IsNeutralCulture)
                    {
                        _specificCulture = culture;
                    } 
                    else
                    { 
                        try 
                        {
                            // note that it's important that we use culture.Name, not culture.IetfLanguageTag, here 
                            culture = CultureInfo.CreateSpecificCulture(culture.Name);
                            _specificCulture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(culture.IetfLanguageTag);
                        }
                        catch (ArgumentException e) 
                        {
                            throw new InvalidOperationException(SR.Get(SRID.XmlLangGetSpecificCulture, _lowerCaseTag), e); 
                        } 
                    }
                } 
            }

            return _specificCulture;
        } 

        ///  
        ///     Finds a registered CultureInfo corresponding to the IetfLanguageTag, or the longest 
        ///       sequence of leading subtags for which we have a registered CultureInfo.
        ///  
        [FriendAccessAllowed]
        internal CultureInfo GetCompatibleCulture()
        {
            if (_compatibleCulture == null) 
            {
                CultureInfo culture = null; 
 
                if (!TryGetEquivalentCulture(out culture))
                { 
                    string languageTag = IetfLanguageTag;

                    do
                    { 
                        languageTag = Shorten(languageTag);
                        if (languageTag == null) 
                        { 
                            // Should never happen, because GetCultureinfoByIetfLanguageTag("") should
                            //  return InvariantCulture! 
                            culture =  CultureInfo.InvariantCulture;
                        }
                        else
                        { 
                            try
                            { 
                                culture = SafeSecurityHelper.GetCultureInfoByIetfLanguageTag(languageTag); 
                            }
                            catch (ArgumentException) 
                            {
                            }
                        }
 
                    }
                    while (culture == null); 
                } 
                _compatibleCulture = culture;
            } 
            return _compatibleCulture;
        }

        ///  
        ///     Checks to see if a second XmlLanguage is included in range of languages specified
        ///       by this XmlLanguage. 
        ///  
        /// 
        ///    In addition to looking for prefix-matches in IetfLanguageTags, the implementation 
        ///      also considers the Parent relationships among registered CultureInfo's.  So, in
        ///      particular, this routine will report that "zh-hk" is in the range specified by
        ///      "zh-hant", even though the latter is not a prefix of the former. And because it
        ///      doesn't restrict itself to traversing CultureInfo.Parent, it will also report that 
        ///      "sr-latn-sp" is in the range covered by "sr-latn".  (Note that "sr-latn" does
        ///      does not have a registered CultureInfo.) 
        ///  
        [FriendAccessAllowed]
        internal bool RangeIncludes(XmlLanguage language) 
        {

            if (this.IsPrefixOf(language.IetfLanguageTag))
            { 
                return true;
            } 
 
            // We still need to do CultureInfo.Parent-aware processing, to cover, for example,
            //  the case that "zh-hant" includes "zh-hk". 
            return RangeIncludes(language.GetCompatibleCulture());
        }

        ///  
        ///     Checks to see if a CultureInfo is included in range of languages specified
        ///       by this XmlLanguage. 
        ///  
        /// 
        ///    In addition to looking for prefix-matches in IetfLanguageTags, the implementation 
        ///      also considers the Parent relationships among CultureInfo's.  So, in
        ///      particular, this routine will report that "zh-hk" is in the range specified by
        ///      "zh-hant", even though the latter is not a prefix of the former.   And because it
        ///      doesn't restrict itself to traversing CultureInfo.Parent, it will also report that 
        ///      "sr-latn-sp" is in the range covered by "sr-latn".  (Note that "sr-latn" does
        ///      does not have a registered CultureInfo.) 
        ///  
        internal bool RangeIncludes(CultureInfo culture)
        { 
            if (culture == null)
            {
                throw new ArgumentNullException("culture");
            } 

            // no need for special cases for InvariantCulture, which has IetfLanguageTag == "" 
 
            // Limit how far we'll walk up the hierarchy to avoid security threat.
            // We could check for cycles (e.g., culture.Parent.Parent == culture) 
            // but in in the case of non-malicious code there should be no cycles,
            // whereas in malicious code, checking for cycles doesn't mitigate the
            // threat; one could always implement Parent such that it always returns
            // a new CultureInfo for which Equals always returns false. 
            for (int i = 0; i < MaxCultureDepth; ++i)
            { 
                // Note that we don't actually insist on a there being CultureInfo corresponding 
                //  to familyMapLanguage.
                // The use of language.StartsWith() catches, for example,the case 
                //  where this="sr-latn", and culture.IetfLanguageTag=="sr-latn-sp".
                // In such a case, culture.Parent.IetfLanguageTag=="sr".
                //  (There is no registered CultureInfo with IetfLanguageTag=="sr-latn".)
                if (this.IsPrefixOf(culture.IetfLanguageTag)) 
                {
                    return true; 
                } 

                CultureInfo parentCulture = culture.Parent; 

                if (parentCulture == null
                        || parentCulture.Equals(CultureInfo.InvariantCulture)
                        || parentCulture == culture) 
                    break;
 
                culture = parentCulture; 
            }
 
            return false;
        }

        ///  
        ///     Compute a measure of specificity of the XmlLanguage, considering both
        ///       subtag length and the CultureInfo.Parent hierarchy. 
        ///  
        internal int GetSpecificity()
        { 
            if (_specificity < 0)
            {
                CultureInfo compatibleCulture = GetCompatibleCulture();
 
                int specificity = GetSpecificity(compatibleCulture, MaxCultureDepth);
 
                if (compatibleCulture != _equivalentCulture) 
                {
                    specificity += GetSubtagCount(_lowerCaseTag) - GetSubtagCount(compatibleCulture.IetfLanguageTag); 
                }

                _specificity = specificity;
            } 

            return _specificity; 
        } 

        ///  
        ///     Helper function for instance-method GetSpecificity.
        /// 
        /// 
        ///     To avoid a security threat, caller provides limit on how far we'll 
        ///       walk up the CultureInfo hierarchy.
        ///       We could check for cycles (e.g., culture.Parent.Parent == culture) 
        ///       but in in the case of non-malicious code there should be no cycles, 
        ///       whereas in malicious code, checking for cycles doesn't mitigate the
        ///       threat; one could always implement Parent such that it always returns 
        ///       a new CultureInfo for which Equals always returns false.
        /// 
        private static int GetSpecificity(CultureInfo culture, int maxDepth)
        { 
            int specificity = 0;
 
            if (maxDepth != 0 && culture != null) 
            {
                string languageTag = culture.IetfLanguageTag; 

                if (languageTag.Length > 0)
                {
                    specificity = Math.Max(GetSubtagCount(languageTag), 1 + GetSpecificity(culture.Parent, maxDepth - 1)); 
                }
            } 
 
            return specificity;
        } 

        private static int GetSubtagCount(string languageTag)
        {
            int tagLength = languageTag.Length; 
            int subtagCount = 0;
 
            if (tagLength > 0) 
            {
                subtagCount = 1; 

                for (int i = 0; i < tagLength; i++)
                {
                    if (languageTag[i] == '-') 
                    {
                        subtagCount += 1; 
                    } 

                } 
            }

            return subtagCount;
        } 

        internal MatchingLanguageCollection MatchingLanguages 
        { 
            get
            { 
                return new MatchingLanguageCollection(this);
            }
        }
 
        // collection of matching languages, ordered from most specific to least specific, starting
        //  with the start language and ending with invariant language ("") 
        internal struct MatchingLanguageCollection : IEnumerable, IEnumerable 
        {
            private XmlLanguage _start; 
            public MatchingLanguageCollection(XmlLanguage start)
            {
                _start = start;
            } 

 
            // strongly typed, avoids boxing 
            public MatchingLanguageEnumerator GetEnumerator()
            { 
                return new MatchingLanguageEnumerator(_start);
            }

            // strongly typed, boxes 
            IEnumerator IEnumerable.GetEnumerator()
            { 
                return GetEnumerator(); 
            }
 
            // weakly typed, boxed
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator(); 
            }
        } 
 
        internal struct MatchingLanguageEnumerator : IEnumerator, IEnumerator
        { 
            private readonly XmlLanguage _start;
            private XmlLanguage _current;
            private bool _atStart;
            private bool _pastEnd; 
            private int _maxCultureDepth;
 
            public MatchingLanguageEnumerator(XmlLanguage start) 
            {
                _start = start; 
                _current = start;
                _pastEnd = false;
                _atStart = true;
                _maxCultureDepth = XmlLanguage.MaxCultureDepth; 
            }
 
            public void Reset() 
            {
                _current = _start; 
                _pastEnd = false;
                _atStart = true;
                _maxCultureDepth = XmlLanguage.MaxCultureDepth;
            } 

            public XmlLanguage Current 
            { 
                get
                { 
                    if (_atStart)
                    {
                        throw new InvalidOperationException(SR.Get(SRID.Enumerator_NotStarted));
                    } 
                    if (_pastEnd)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.Enumerator_ReachedEnd)); 
                    }
 
                    return _current;
                }
            }
 
            public bool MoveNext()
            { 
                if (_atStart) 
                {
                    _atStart = false; 
                    return true;
                }
                else if (_current.IetfLanguageTag.Length == 0)
                { 
                    _atStart = false;
                    _pastEnd = true; 
                    return false; 
                }
                else 
                {
                    XmlLanguage prefixLanguage = _current.PrefixLanguage;
                    CultureInfo culture = null;
 
                    if (_maxCultureDepth > 0)
                    { 
                        if (_current.TryGetEquivalentCulture(out culture)) 
                        {
                            culture = culture.Parent; 
                        }
                        else
                        {
                            culture = null; 
                        }
                    } 
 
                    if (culture == null)
                    { 
                        _current = prefixLanguage;
                        _atStart = false;
                        return true;
                    } 
                    else
                    { 
                        // We MUST NOT call the private constructor, but instead call GetLanguage()! 
                        XmlLanguage parentLanguage = XmlLanguage.GetLanguage(culture.IetfLanguageTag);
 
                        if (parentLanguage.IsPrefixOf(prefixLanguage.IetfLanguageTag))
                        {
                            _current = prefixLanguage;
                            _atStart = false; 
                            return true;
                        } 
                        else 
                        {
                            // We definitely do this if 
                            //   prefixLanguage.IsPrefixOf(parentLanguage.IetfLanguageTag)
                            // But if this is not true, then we are faced with a problem with
                            //   divergent paths between prefix-tags and parent-CultureInfos.
                            //   This code makes the arbitrary decision to follow the parent 
                            //   path when faced with this divergence.
                            // 
 

 

                            //
                            _maxCultureDepth -= 1;
                            _current = parentLanguage; 
                            _atStart = false;
                            return true; 
                        } 
                    }
                } 
            }

            object IEnumerator.Current
            { 
                get
                { 
                    return Current; 
                }
            } 

            void IDisposable.Dispose() { }

        } 

 
 
        /// 
        ///     Differs from calling string.StartsWith, because each subtag must match 
        ///         in its entirety.
        ///     Note that this routine returns true if the tags match.
        /// 
        private bool IsPrefixOf(string longTag) 
        {
            string prefix = IetfLanguageTag; 
 

            // if we fail a simple string-prefix test, we know we don't have a subtag-prefix. 
            if(!longTag.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            } 

            // string-prefix test passed -- now determine if we're at a subtag-boundary 
            return (prefix.Length == 0 || prefix.Length == longTag.Length || longTag[prefix.Length] == '-'); 
        }
 
        private bool TryGetEquivalentCulture(out CultureInfo culture)
        {
            culture = null;
 
            if (_equivalentCulture == null && !_equivalentCultureFailed)
            { 
                try 
                {
                    GetEquivalentCulture(); 
                }
                catch (InvalidOperationException)
                {
                } 
            }
 
            culture = _equivalentCulture; 

            return (culture != null); 
        }

        private XmlLanguage PrefixLanguage
        { 
            get
            { 
                string prefix = Shorten(IetfLanguageTag);   // can return null 

                // We MUST NOT call the private constructor, but instead call GetLanguage()! 
                return XmlLanguage.GetLanguage(prefix);     // throws on null
            }
        }
 
        /// 
        ///     Shorten a well-formed RFC 3066 string by one subtag. 
        ///  
        /// 
        ///     Shortens "" into null. 
        /// 
        private static string Shorten(string languageTag)
        {
            if (languageTag.Length == 0) 
            {
                return null; 
            } 

            int i = languageTag.Length - 1; 

            while (languageTag[i] != '-' && i > 0)
            {
                i -= 1; 
            }
 
            // i now contains of index of first character to be omitted from smaller tag 
            return languageTag.Substring (0, i);
        } 



 
        /// 
        ///     Throws an ArgumentException (or ArgumentNullException) is not the empty 
        ///       string, and does not conform to RFC 3066. 
        /// 
        ///  
        ///     It is assumed that caller has already converted to lower-case.
        ///     The language string may be empty, or else must conform to RFC 3066 rules:
        ///     The first subtag must consist of only ASCII letters.
        ///     Additional subtags must consist ASCII letters or numerals. 
        ///     Subtags are separated by a single hyphen character.
        ///     Every subtag must be 1 to 8 characters long. 
        ///     No leading or trailing hyphens are permitted. 
        /// 
        ///  
        /// tag is NULL.
        /// tag is non-empty, but does not conform to RFC 3066.
        private static void ValidateLowerCaseTag(string ietfLanguageTag)
        { 
            if (ietfLanguageTag == null)
            { 
                throw new ArgumentNullException("ietfLanguageTag"); 
            }
 
            if (ietfLanguageTag.Length > 0)
            {
                using (StringReader reader = new StringReader(ietfLanguageTag))
                { 
                    int i;
 
                    i = ParseSubtag(ietfLanguageTag, reader, /* isPrimary */ true); 
                    while (i != -1)
                    { 
                        i = ParseSubtag(ietfLanguageTag, reader, /* isPrimary */ false);
                    }
                }
            } 
        }
 
        // returns the character which terminated the subtag -- either '-' or -1 for 
        //  end of string.
        // throws exception on improper formatting 
        // It is assumed that caller has already converted to lower-case.
        static private int ParseSubtag(string ietfLanguageTag, StringReader reader, bool isPrimary)
        {
            int c; 
            bool ok;
            const int maxCharsPerSubtag = 8; 
 
            c = reader.Read();
 
            ok = IsLowerAlpha(c);
            if (!ok && !isPrimary)
                ok = IsDigit(c);
 
            if (!ok)
            { 
                ThrowParseException(ietfLanguageTag); 
            }
 
            int charsRead = 1;
            for (;;)
            {
                c = reader.Read(); 
                charsRead++;
 
                ok = IsLowerAlpha(c); 
                if (!ok && !isPrimary)
                { 
                    ok = IsDigit(c);
                }

                if (!ok) 
                {
                    if (c == -1 || c == '-') 
                    { 
                        return c;
                    } 
                    else
                    {
                        ThrowParseException(ietfLanguageTag);
                    } 
                }
                else 
                { 
                    if (charsRead > maxCharsPerSubtag)
                    { 
                        ThrowParseException(ietfLanguageTag);
                    }
                }
            } 
        }
 
        static private bool IsLowerAlpha(int c) 
        {
            return (c >= 'a' && c <= 'z'); 
        }

        static private bool IsDigit(int c)
        { 
            return c >= '0' && c <= '9';
        } 
 
        static private void ThrowParseException(string ietfLanguageTag)
        { 
             throw new ArgumentException(SR.Get(SRID.XmlLangMalformed, ietfLanguageTag), "ietfLanguageTag");
        }

        // throws if there is a non-7-bit ascii character 
        static private string AsciiToLower(string tag)
        { 
            int length = tag.Length; 

            for (int i = 0; i < length; i++) 
            {
                if (tag[i] > 127)
                {
                    ThrowParseException(tag); 
                }
            } 
 
            return tag.ToLowerInvariant();
        } 

    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK