File: Internal\SrgsParser\SrgsDocumentParser.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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Speech.Recognition;
using System.Speech.Recognition.SrgsGrammar;

namespace System.Speech.Internal.SrgsParser
{
    internal class SrgsDocumentParser : ISrgsParser
    {
        #region Constructors

        internal SrgsDocumentParser(SrgsGrammar grammar)
        {
            _grammar = grammar;
        }

        #endregion

        #region Internal Methods

        // Initializes the object from a stream containing SRGS in XML
        public void Parse()
        {
            try
            {
                ProcessGrammarElement(_grammar, _parser.Grammar);
            }
            catch
            {
                // clear all the rules
                _parser.RemoveAllRules();
                throw;
            }
        }

        #endregion

        #region Internal Properties

        public IElementFactory ElementFactory
        {
            set
            {
                _parser = value;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Process the top level grammar element
        /// </summary>
        private void ProcessGrammarElement(SrgsGrammar source, IGrammar grammar)
        {
            grammar.Culture = source.Culture;
            grammar.Mode = source.Mode;
            if (source.Root != null)
            {
                grammar.Root = source.Root.Id;
            }
            grammar.TagFormat = source.TagFormat;
            grammar.XmlBase = source.XmlBase;
            grammar.GlobalTags = source.GlobalTags;
            grammar.PhoneticAlphabet = source.PhoneticAlphabet;

            // Process child elements.
            foreach (SrgsRule srgsRule in source.Rules)
            {
                IRule rule = ParseRule(grammar, srgsRule);
                rule.PostParse(grammar);
            }
            grammar.AssemblyReferences = source.AssemblyReferences;
            grammar.CodeBehind = source.CodeBehind;
            grammar.Debug = source.Debug;
            grammar.ImportNamespaces = source.ImportNamespaces;
            grammar.Language = source.Language ?? "C#";
            grammar.Namespace = source.Namespace;

            // if add the content to the generic _scrip
            _parser.AddScript(grammar, source.Script, null, -1);
            // Finish all initialization - should check for the Root and the all
            // rules are defined
            grammar.PostParse(null!);
        }

        /// <summary>
        /// Parse a rule
        /// </summary>
        private IRule ParseRule(IGrammar grammar, SrgsRule srgsRule)
        {
            string id = srgsRule.Id;
            bool hasScript = srgsRule.OnInit != null || srgsRule.OnParse != null || srgsRule.OnError != null || srgsRule.OnRecognition != null;
            IRule rule = grammar.CreateRule(id, srgsRule.Scope == SrgsRuleScope.Public ? RulePublic.True : RulePublic.False, srgsRule.Dynamic, hasScript);

            if (srgsRule.OnInit != null)
            {
                rule.CreateScript(grammar, id, srgsRule.OnInit, RuleMethodScript.onInit);
            }

            if (srgsRule.OnParse != null)
            {
                rule.CreateScript(grammar, id, srgsRule.OnParse, RuleMethodScript.onParse);
            }

            if (srgsRule.OnError != null)
            {
                rule.CreateScript(grammar, id, srgsRule.OnError, RuleMethodScript.onError);
            }

            if (srgsRule.OnRecognition != null)
            {
                rule.CreateScript(grammar, id, srgsRule.OnRecognition, RuleMethodScript.onRecognition);
            }

            // Add the code to the backend
            if (srgsRule.Script.Length > 0)
            {
                _parser.AddScript(grammar, id, srgsRule.Script);
            }

            rule.BaseClass = srgsRule.BaseClass;

            foreach (SrgsElement srgsElement in GetSortedTagElements(srgsRule.Elements))
            {
                ProcessChildNodes(srgsElement, rule, rule);
            }
            return rule;
        }

        /// <summary>
        /// Parse a ruleref
        /// </summary>
        private IRuleRef ParseRuleRef(SrgsRuleRef srgsRuleRef, IElement parent)
        {
            IRuleRef? ruleRef = null;
            bool fSpecialRuleRef = true;

            if (srgsRuleRef == SrgsRuleRef.Null)
            {
                ruleRef = _parser.Null;
            }
            else if (srgsRuleRef == SrgsRuleRef.Void)
            {
                ruleRef = _parser.Void;
            }
            else if (srgsRuleRef == SrgsRuleRef.Garbage)
            {
                ruleRef = _parser.Garbage;
            }
            else
            {
                System.Diagnostics.Debug.Assert(srgsRuleRef.Uri != null, "If it is not a special rule ref, the uri is not null");
                ruleRef = _parser.CreateRuleRef(parent, srgsRuleRef.Uri, srgsRuleRef.SemanticKey, srgsRuleRef.Params);
                fSpecialRuleRef = false;
            }

            if (fSpecialRuleRef)
            {
                _parser.InitSpecialRuleRef(parent, ruleRef);
            }

            ruleRef.PostParse(parent);
            return ruleRef;
        }

        /// <summary>
        /// Parse a One-Of
        /// </summary>
        private IOneOf ParseOneOf(SrgsOneOf srgsOneOf, IElement parent, IRule rule)
        {
            IOneOf oneOf = _parser.CreateOneOf(parent, rule);

            // Process child elements.
            foreach (SrgsItem item in srgsOneOf.Items)
            {
                ProcessChildNodes(item, oneOf, rule);
            }
            oneOf.PostParse(parent);
            return oneOf;
        }

        /// <summary>
        /// Parse Item
        /// </summary>
        private IItem ParseItem(SrgsItem srgsItem, IElement parent, IRule rule)
        {
            IItem item = _parser.CreateItem(parent, rule, srgsItem.MinRepeat, srgsItem.MaxRepeat, srgsItem.RepeatProbability, srgsItem.Weight);

            // Process child elements.
            foreach (SrgsElement srgsElement in GetSortedTagElements(srgsItem.Elements))
            {
                ProcessChildNodes(srgsElement, item, rule);
            }

            item.PostParse(parent);
            return item;
        }

        /// <summary>
        /// Parse Token
        /// </summary>
        private IToken ParseToken(SrgsToken srgsToken, IElement parent)
        {
            return _parser.CreateToken(parent, srgsToken.Text, srgsToken.Pronunciation, srgsToken.Display, -1);
        }

        /// <summary>
        /// Break the string into individual tokens and ParseToken() each individual token.
        ///
        /// Token string is a sequence of 0 or more white space delimited tokens.
        /// Tokens may also be delimited by double quotes.  In these cases, the double
        /// quotes token must be surrounded by white space or string boundary.
        /// </summary>
        private void ParseText(IElement parent, string sChars, string? pronunciation, string? display, float reqConfidence)
        {
            System.Diagnostics.Debug.Assert((parent != null) && (!string.IsNullOrEmpty(sChars)));

            XmlParser.ParseText(parent, sChars, pronunciation, display, reqConfidence, new CreateTokenCallback(_parser.CreateToken));
        }

        /// <summary>
        /// Parse tag
        /// </summary>
        private ISubset ParseSubset(SrgsSubset srgsSubset, IElement parent)
        {
            MatchMode matchMode = MatchMode.Subsequence;

            switch (srgsSubset.MatchingMode)
            {
                case SubsetMatchingMode.OrderedSubset:
                    matchMode = MatchMode.OrderedSubset;
                    break;

                case SubsetMatchingMode.OrderedSubsetContentRequired:
                    matchMode = MatchMode.OrderedSubsetContentRequired;
                    break;

                case SubsetMatchingMode.Subsequence:
                    matchMode = MatchMode.Subsequence;
                    break;

                case SubsetMatchingMode.SubsequenceContentRequired:
                    matchMode = MatchMode.SubsequenceContentRequired;
                    break;
            }
            return _parser.CreateSubset(parent, srgsSubset.Text, matchMode);
        }

        /// <summary>
        /// Parse tag
        /// </summary>
        private ISemanticTag ParseSemanticTag(SrgsSemanticInterpretationTag srgsTag, IElement parent)
        {
            ISemanticTag tag = _parser.CreateSemanticTag(parent);

            tag.Content(parent, srgsTag.Script, 0);
            tag.PostParse(parent);
            return tag;
        }

        /// <summary>
        /// ParseNameValueTag tag
        /// </summary>
        private IPropertyTag ParseNameValueTag(SrgsNameValueTag srgsTag, IElement parent)
        {
            IPropertyTag tag = _parser.CreatePropertyTag(parent);

            // Initialize the tag
            tag.NameValue(parent, srgsTag.Name, srgsTag.Value);

            // Since the tag are always pushed at the end of the element list, they can be committed right away
            tag.PostParse(parent);
            return tag;
        }

        /// <summary>
        /// Calls the appropriate Parsing function based on the element type
        /// </summary>
        private void ProcessChildNodes(SrgsElement srgsElement, IElement parent, IRule rule)
        {
            Type elementType = srgsElement.GetType();
            IElement child;
            IRule? parentRule = parent as IRule;
            IItem? parentItem = parent as IItem;

            if (elementType == typeof(SrgsRuleRef))
            {
                child = ParseRuleRef((SrgsRuleRef)srgsElement, parent);
            }
            else if (elementType == typeof(SrgsOneOf))
            {
                child = ParseOneOf((SrgsOneOf)srgsElement, parent, rule);
            }
            else if (elementType == typeof(SrgsItem))
            {
                child = ParseItem((SrgsItem)srgsElement, parent, rule);
            }
            else if (elementType == typeof(SrgsToken))
            {
                child = ParseToken((SrgsToken)srgsElement, parent);
            }
            else if (elementType == typeof(SrgsNameValueTag))
            {
                child = ParseNameValueTag((SrgsNameValueTag)srgsElement, parent);
            }
            else if (elementType == typeof(SrgsSemanticInterpretationTag))
            {
                child = ParseSemanticTag((SrgsSemanticInterpretationTag)srgsElement, parent);
            }
            else if (elementType == typeof(SrgsSubset))
            {
                child = ParseSubset((SrgsSubset)srgsElement, parent);
            }
            else if (elementType == typeof(SrgsText))
            {
                SrgsText srgsText = (SrgsText)srgsElement;
                string content = srgsText.Text;

                // Create the SrgsElement for the text
                IElementText textChild = _parser.CreateText(parent, content);

                // Split it in pieces
                ParseText(parent, content, null, null, -1f);

                if (parentRule != null)
                {
                    _parser.AddElement(parentRule, textChild);
                }
                else
                {
                    if (parentItem != null)
                    {
                        _parser.AddElement(parentItem, textChild);
                    }
                    else
                    {
                        XmlParser.ThrowSrgsException(SRID.InvalidElement);
                    }
                }
                child = null!;
            }
            else
            {
                child = null!;
                System.Diagnostics.Debug.Fail("Unsupported Srgs element");
                XmlParser.ThrowSrgsException(SRID.InvalidElement);
            }

            // if the parent is a one of, then the children must be an Item
            if (parent is IOneOf parentOneOf)
            {
                if (child is IItem childItem)
                {
                    _parser.AddItem(parentOneOf, childItem);
                }
                else
                {
                    XmlParser.ThrowSrgsException(SRID.InvalidElement);
                }
            }
            else
            {
                if (parentRule != null)
                {
                    _parser.AddElement(parentRule, child);
                }
                else
                {
                    if (parentItem != null)
                    {
                        _parser.AddElement(parentItem, child);
                    }
                    else
                    {
                        XmlParser.ThrowSrgsException(SRID.InvalidElement);
                    }
                }
            }
        }

        private IEnumerable<SrgsElement> GetSortedTagElements(Collection<SrgsElement> elements)
        {
            if (_grammar.TagFormat == SrgsTagFormat.KeyValuePairs)
            {
                List<SrgsElement> list = new();
                foreach (SrgsElement element in elements)
                {
                    if (element is not SrgsNameValueTag)
                    {
                        list.Add(element);
                    }
                }
                foreach (SrgsElement element in elements)
                {
                    if ((element is SrgsNameValueTag))
                    {
                        list.Add(element);
                    }
                }
                return list;
            }
            else
            {
                // Semantic Interpretation, the order for the tag element does not matter
                return elements;
            }
        }

        #endregion

        #region Private Fields

        private SrgsGrammar _grammar;

        private IElementFactory _parser = null!; // Correct usage relies on ElementFactory being set

        #endregion
    }
}