File: Recognition\SrgsGrammar\SrgsRuleRef.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.ComponentModel;
using System.Diagnostics;
using System.Speech.Internal;
using System.Speech.Internal.SrgsParser;
using System.Text;
using System.Xml;

namespace System.Speech.Recognition.SrgsGrammar
{
    [Serializable]
    [ImmutableObject(true)]
    [DebuggerDisplay("{DebuggerDisplayString()}")]
    public class SrgsRuleRef : SrgsElement, IRuleRef
    {
        #region Constructors
        public SrgsRuleRef(Uri uri)
        {
            UriInit(uri, null, null, null);
        }
        public SrgsRuleRef(Uri uri, string rule)
        {
            Helpers.ThrowIfEmptyOrNull(rule, nameof(rule));

            UriInit(uri, rule, null, null);
        }
        public SrgsRuleRef(Uri uri, string rule, string semanticKey)
        {
            Helpers.ThrowIfEmptyOrNull(semanticKey, nameof(semanticKey));

            UriInit(uri, rule, semanticKey, null);
        }
        public SrgsRuleRef(Uri uri, string rule, string semanticKey, string parameters)
        {
            Helpers.ThrowIfEmptyOrNull(parameters, nameof(parameters));

            UriInit(uri, rule, semanticKey, parameters);
        }
        public SrgsRuleRef(SrgsRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            _uri = new Uri("#" + rule.Id, UriKind.Relative);
        }
        public SrgsRuleRef(SrgsRule rule, string semanticKey)
            : this(rule)
        {
            Helpers.ThrowIfEmptyOrNull(semanticKey, nameof(semanticKey));

            _semanticKey = semanticKey;
        }
        public SrgsRuleRef(SrgsRule rule, string semanticKey, string parameters)
            : this(rule)
        {
            Helpers.ThrowIfEmptyOrNull(parameters, nameof(parameters));

            _semanticKey = semanticKey;
            _params = parameters;
        }

        /// <summary>
        /// Special private constructor for Special Rulerefs
        /// </summary>
        private SrgsRuleRef(SpecialRuleRefType type)
        {
            _type = type;
        }

        internal SrgsRuleRef(string? semanticKey, string? parameters, Uri uri)
        {
            _uri = uri;
            _semanticKey = semanticKey;
            _params = parameters;
        }

        #endregion

        #region public Properties
        // Uri of the rule this rule reference references.
        public Uri? Uri
        {
            get
            {
                return _uri;
            }
        }

        /// <summary>
        /// Set the semanticKey for a Ruleref
        /// </summary>
        public string? SemanticKey
        {
            get
            {
                return _semanticKey;
            }
        }

        /// <summary>
        /// Set the init parameters for a Ruleref
        /// </summary>
        public string? Params
        {
            get
            {
                return _params;
            }
        }
        // The Null SpecialRuleRef defines a rule that is automatically matched:
        // that is, matched without the user speaking any word.
        public static readonly SrgsRuleRef Null = new(SpecialRuleRefType.Null);
        // The Void SpecialRuleRef defines a rule that can never be spoken. Inserting
        // VOID into a sequence automatically makes that sequence unspeakable.
        public static readonly SrgsRuleRef Void = new(SpecialRuleRefType.Void);
        // The Garbage SpecialRuleRef defines a rule that may match any speech up until
        // the next rule match, the next token or until the end of spoken input.
        public static readonly SrgsRuleRef Garbage = new(SpecialRuleRefType.Garbage);
        public static readonly SrgsRuleRef Dictation = new(new Uri("grammar:dictation"));
        public static readonly SrgsRuleRef MnemonicSpelling = new(new Uri("grammar:dictation#spelling"));

        #endregion

        #region Internal methods

        internal override void WriteSrgs(XmlWriter writer)
        {
            // Write <ruleref _uri="_uri" />
            writer.WriteStartElement("ruleref");
            if (_uri != null)
            {
                writer.WriteAttributeString("uri", _uri.ToString());
            }
            else
            {
                string special;
                switch (_type)
                {
                    case SpecialRuleRefType.Null:
                        special = "NULL";
                        break;

                    case SpecialRuleRefType.Void:
                        special = "VOID";
                        break;

                    case SpecialRuleRefType.Garbage:
                        special = "GARBAGE";
                        break;

                    default:
                        XmlParser.ThrowSrgsException(SRID.InvalidSpecialRuleRef);
                        special = null;
                        break;
                }
                writer.WriteAttributeString("special", special);
            }

            // Write the 'name' attribute
            if (_semanticKey != null)
            {
                writer.WriteAttributeString("sapi", "semantic-key", XmlParser.sapiNamespace, _semanticKey);
            }

            // Write the 'params' attribute
            if (_params != null)
            {
                writer.WriteAttributeString("sapi", "params", XmlParser.sapiNamespace, _params);
            }

            writer.WriteEndElement();
        }

        /// <summary>
        /// Validate the SRGS element.
        /// </summary>
        internal override void Validate(SrgsGrammar grammar)
        {
            bool fScript = _params != null || _semanticKey != null;
            grammar._fContainsCode |= fScript;
            grammar.HasSapiExtension |= fScript;

            // Validate _uri
            if (_uri != null)
            {
                string sUri = _uri.ToString();
                if (sUri[0] == '#')
                {
                    bool uriFound = false;
                    if (sUri.StartsWith("#grammar:dictation", StringComparison.Ordinal))
                    {
                        uriFound = true;
                    }
                    else
                    {
                        sUri = sUri.Substring(1);
                        foreach (SrgsRule rule in grammar.Rules)
                        {
                            if (rule.Id == sUri)
                            {
                                uriFound = true;
                                break;
                            }
                        }
                    }

                    if (!uriFound)
                    {
                        XmlParser.ThrowSrgsException(SRID.UndefRuleRef, sUri);
                    }
                }
            }

            base.Validate(grammar);
        }

        internal override string DebuggerDisplayString()
        {
            StringBuilder sb = new("SrgsRuleRef");
            if (_uri != null)
            {
                sb.Append(" uri = '");
                sb.Append(_uri.ToString());
                sb.Append('\'');
            }
            else
            {
                sb.Append(" special = '");
                sb.Append(_type.ToString());
                sb.Append('\'');
            }
            return sb.ToString();
        }

        #endregion

        #region Private Method

        /// <summary>
        /// Call by constructors. No check is made on the parameters except for the Uri
        /// </summary>
        private void UriInit(Uri uri, string? rule, string? semanticKey, string? initParameters)
        {
            ArgumentNullException.ThrowIfNull(uri);

            if (string.IsNullOrEmpty(rule))
            {
                _uri = uri;
            }
            else
            {
                _uri = new Uri(uri.ToString() + "#" + rule, UriKind.RelativeOrAbsolute);
            }
            _semanticKey = semanticKey;
            _params = initParameters;
        }

        #endregion

        #region Private Fields

        #region Private Enums
        // Special rule references allow grammars based on CFGs to have powerful
        // additional features, such as transitions into dictation (both recognized
        // or not recognized) and word sequences from SAPI 5.0.
        private enum SpecialRuleRefType
        {
            // Defines a rule that is automatically matched that is, matched without
            // the user speaking any word.
            Null,
            // Defines a rule that can never be spoken. Inserting VOID into a sequence
            // automatically makes that sequence unspeakable.
            Void,
            // Defines a rule that may match any speech up until the next rule match,
            // the next token or until the end of spoken input.
            // Designed for applications that would like to recognize some phrases
            // without failing due to irrelevant, or ignorable words.
            Garbage,
        }

        #endregion

        // if the uri is null then it is a special rule ref
        private Uri? _uri;

        private SpecialRuleRefType _type;

        // Alias string for the semantic dictionary
        private string? _semanticKey;

        // Alias string for the semantic dictionary
        private string? _params;

        #endregion
    }
}