File: Recognition\SrgsGrammar\SrgsRule.cs
Web Access
Project: src\src\runtime\src\libraries\System.Speech\src\System.Speech.csproj (System.Speech)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Speech.Internal;
using System.Speech.Internal.SrgsParser;
using System.Xml;

namespace System.Speech.Recognition.SrgsGrammar
{
    [Serializable]
    [DebuggerDisplay("Rule = {_id.ToString()}, Scope = {_scope.ToString()}")]
    [DebuggerTypeProxy(typeof(SrgsRuleDebugDisplay))]
    public class SrgsRule : IRule
    {
        private static readonly SearchValues<char> s_invalidChars = SearchValues.Create("?*+|()^$/;.=<>[]{}\\ \t\r\n");

        #region Constructors

        public SrgsRule(string id)
        {
            XmlParser.ValidateRuleId(id);
            _id = id;
            _elements = new SrgsElementList();
        }
        public SrgsRule(string id, params SrgsElement[] elements)
        {
            ArgumentNullException.ThrowIfNull(elements);

            XmlParser.ValidateRuleId(id);
            _id = id;
            _elements = new SrgsElementList();

            for (int iElement = 0; iElement < elements.Length; iElement++)
            {
                if (elements[iElement] == null)
                {
                    throw new ArgumentNullException(nameof(elements), SR.Get(SRID.ParamsEntryNullIllegal));
                }
                _elements.Add(elements[iElement]);
            }
        }

        #endregion

        #region public Method
        public void Add(SrgsElement element)
        {
            ArgumentNullException.ThrowIfNull(element);

            Elements.Add(element);
        }

        #endregion

        #region public Properties
        public Collection<SrgsElement> Elements
        {
            get
            {
                return _elements;
            }
        }

        public string Id
        {
            get
            {
                return _id;
            }
            set
            {
                XmlParser.ValidateRuleId(value);
                _id = value;
            }
        }
        public SrgsRuleScope Scope
        {
            get
            {
                return _scope;
            }
            set
            {
                _scope = value;
                _isScopeSet = true;
            }
        }

        /// <summary>
        /// classname
        /// </summary>
        public string? BaseClass
        {
            get
            {
                return _baseclass;
            }
            set
            {
                // base value can be null
#pragma warning disable 56526
                _baseclass = value;
#pragma warning restore 56526
            }
        }

        /// <summary>
        /// OnInit
        /// </summary>
        public string Script
        {
            get
            {
                return _script;
            }
            set
            {
                Helpers.ThrowIfEmptyOrNull(value, nameof(value));
                _script = value;
            }
        }

        /// <summary>
        /// OnInit
        /// </summary>
        public string? OnInit
        {
            get
            {
                return _onInit;
            }
            set
            {
                ValidateIdentifier(value);
                _onInit = value;
            }
        }

        /// <summary>
        /// OnParse
        /// </summary>
        public string? OnParse
        {
            get
            {
                return _onParse;
            }
            set
            {
                ValidateIdentifier(value);
                _onParse = value;
            }
        }

        /// <summary>
        /// OnError
        /// </summary>
        public string? OnError
        {
            get
            {
                return _onError;
            }
            set
            {
                ValidateIdentifier(value);
                _onError = value;
            }
        }

        /// <summary>
        /// OnRecognition
        /// </summary>
        public string? OnRecognition
        {
            get
            {
                return _onRecognition;
            }
            set
            {
                ValidateIdentifier(value);
                _onRecognition = value;
            }
        }

        #endregion

        #region Internal Methods

        internal void WriteSrgs(XmlWriter writer)
        {
            // Empty rule are not allowed
            if (Elements.Count == 0)
            {
                XmlParser.ThrowSrgsException(SRID.InvalidEmptyRule, "rule", _id);
            }

            // Write <rule id="MyRule" scope="public">
            writer.WriteStartElement("rule");
            writer.WriteAttributeString("id", _id);
            if (_isScopeSet)
            {
                switch (_scope)
                {
                    case SrgsRuleScope.Private:
                        writer.WriteAttributeString("scope", "private");
                        break;

                    case SrgsRuleScope.Public:
                        writer.WriteAttributeString("scope", "public");
                        break;
                }
            }
            // Write the 'baseclass' attribute
            if (_baseclass != null)
            {
                writer.WriteAttributeString("sapi", "baseclass", XmlParser.sapiNamespace, _baseclass);
            }
            // Write <rule id="MyRule" sapi:dynamic="true">
            if (_dynamic != RuleDynamic.NotSet)
            {
                writer.WriteAttributeString("sapi", "dynamic", XmlParser.sapiNamespace, _dynamic == RuleDynamic.True ? "true" : "false");
            }

            // Write the 'onInit' code snippet
            if (OnInit != null)
            {
                writer.WriteAttributeString("sapi", "onInit", XmlParser.sapiNamespace, OnInit);
            }

            // Write <rule onParse="symbol">
            if (OnParse != null)
            {
                writer.WriteAttributeString("sapi", "onParse", XmlParser.sapiNamespace, OnParse);
            }

            // Write <rule onError="symbol">
            if (OnError != null)
            {
                writer.WriteAttributeString("sapi", "onError", XmlParser.sapiNamespace, OnError);
            }

            // Write <rule onRecognition="symbol">
            if (OnRecognition != null)
            {
                writer.WriteAttributeString("sapi", "onRecognition", XmlParser.sapiNamespace, OnRecognition);
            }
            // Write <rule> body and footer.
            Type? previousElementType = null;

            foreach (SrgsElement element in _elements)
            {
                // Insert space between consecutive SrgsText elements.
                Type elementType = element.GetType();

                if ((elementType == typeof(SrgsText)) && (elementType == previousElementType))
                {
                    writer.WriteString(" ");
                }

                previousElementType = elementType;
                element.WriteSrgs(writer);
            }

            writer.WriteEndElement();

            // Write the <script> elements for the OnParse, OnError and OnRecognition code.
            // At the bottom of the code
            if (HasCode)
            {
                WriteScriptElement(writer, _script);
            }
        }

        // Validate the SRGS element.
        /// <summary>
        /// Validate each element and recurse through all the children srgs
        /// elements if any.
        /// </summary>
        internal void Validate(SrgsGrammar grammar)
        {
            bool fScript = HasCode || _onInit != null || _onParse != null || _onError != null || _onRecognition != null || _baseclass != null;
            grammar._fContainsCode |= fScript;
            grammar.HasSapiExtension |= fScript;

            if (_dynamic != RuleDynamic.NotSet)
            {
                grammar.HasSapiExtension = true;
            }

            if (OnInit != null && Scope != SrgsRuleScope.Public)
            {
                XmlParser.ThrowSrgsException(SRID.OnInitOnPublicRule, "OnInit", Id);
            }

            if (OnRecognition != null && Scope != SrgsRuleScope.Public)
            {
                XmlParser.ThrowSrgsException(SRID.OnInitOnPublicRule, "OnRecognition", Id);
            }
            // Validate all the children
            foreach (SrgsElement element in _elements)
            {
                element.Validate(grammar);
            }
        }

        void IElement.PostParse(IElement grammar)
        {
            ((SrgsGrammar)grammar).Rules.Add(this);
        }

        void IRule.CreateScript(IGrammar grammar, string rule, string method, RuleMethodScript type)
        {
            switch (type)
            {
                case RuleMethodScript.onInit:
                    _onInit = method;
                    break;

                case RuleMethodScript.onParse:
                    _onParse = method;
                    break;

                case RuleMethodScript.onRecognition:
                    _onRecognition = method;
                    break;

                case RuleMethodScript.onError:
                    _onError = method;
                    break;

                default:
                    // unknown method!!!
                    System.Diagnostics.Debug.Assert(false);
                    break;
            }
        }

        #endregion

        #region Internal Properties
        internal RuleDynamic Dynamic
        {
            get
            {
                return _dynamic;
            }
            set
            {
                _dynamic = value;
            }
        }

        internal bool HasCode
        {
            get
            {
                return _script.Length > 0;
            }
        }

        #endregion

        #region Private Methods

        private void WriteScriptElement(XmlWriter writer, string sCode)
        {
            writer.WriteStartElement("sapi", "script", XmlParser.sapiNamespace);
            writer.WriteAttributeString("sapi", "rule", XmlParser.sapiNamespace, _id);
            writer.WriteCData(sCode);
            writer.WriteEndElement();
        }

#pragma warning disable 56507 // check for null or empty strings

        private void ValidateIdentifier(string? s)
        {
            if (s == _id)
            {
                XmlParser.ThrowSrgsException(SRID.ConstructorNotAllowed, _id);
            }

            if (s != null && (s.Length == 0 || s.AsSpan().ContainsAny(s_invalidChars)))
            {
                XmlParser.ThrowSrgsException(SRID.InvalidMethodName);
            }
        }

        #endregion

        #region Private Fields

        private SrgsElementList _elements;

        private string _id;

        private SrgsRuleScope _scope = SrgsRuleScope.Private;

        private RuleDynamic _dynamic = RuleDynamic.NotSet;

        private bool _isScopeSet;

        // class name for the code behind
        private string? _baseclass;

        // .NET Language for this grammar
        private string _script = string.Empty;

        private string? _onInit;

        private string? _onParse;

        private string? _onError;

        private string? _onRecognition;

        #endregion

        #region Private Types

        // Used by the debugger display attribute
        internal class SrgsRuleDebugDisplay
        {
            public SrgsRuleDebugDisplay(SrgsRule rule)
            {
                _rule = rule;
            }

            public object Id
            {
                get
                {
                    return _rule.Id;
                }
            }

            public object Scope
            {
                get
                {
                    return _rule.Scope;
                }
            }

            public object? BaseClass
            {
                get
                {
                    return _rule.BaseClass;
                }
            }

            public object Script
            {
                get
                {
                    return _rule.Script;
                }
            }

            public object? OnInit
            {
                get
                {
                    return _rule.OnInit;
                }
            }

            public object? OnParse
            {
                get
                {
                    return _rule.OnParse;
                }
            }

            public object? OnError
            {
                get
                {
                    return _rule.OnError;
                }
            }

            public object? OnRecognition
            {
                get
                {
                    return _rule.OnRecognition;
                }
            }

            public object Count
            {
                get
                {
                    return _rule._elements.Count;
                }
            }
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public SrgsElement[] AKeys
            {
                get
                {
                    SrgsElement[] elements = new SrgsElement[_rule._elements.Count];
                    for (int i = 0; i < _rule._elements.Count; i++)
                    {
                        elements[i] = _rule._elements[i];
                    }
                    return elements;
                }
            }

            private SrgsRule _rule;
        }

        #endregion
    }

    #region Public Enums
    // SrgsRuleScope specifies how a rule behaves with respect to being able to be
    // referenced by other rules, and whether or not the rule can be activated
    // or not.
    public enum SrgsRuleScope
    {
        // Public rules can be both activated as well as referenced by rules in other grammars
        Public,
        // Private rules can not be activated, but they can be referenced by rules in the same grammar
        Private
    };

    #endregion
}