File: Internal\SrgsCompiler\SrgsElementCompilerFactory.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.

#region Using directives

using System.Globalization;
using System.Speech.Internal.SrgsParser;

#endregion

namespace System.Speech.Internal.SrgsCompiler
{
    internal class SrgsElementCompilerFactory : IElementFactory
    {
        #region Constructors

        internal SrgsElementCompilerFactory(Backend backend, CustomGrammar cg)
        {
            _backend = backend;
            _cg = cg;
            _grammar = new GrammarElement(backend, cg);
        }
        #endregion

        #region Internal Methods

        /// <summary>
        /// Clear all the rules
        /// </summary>
        void IElementFactory.RemoveAllRules()
        {
        }

        IPropertyTag IElementFactory.CreatePropertyTag(IElement parent)
        {
            return new PropertyTag((ParseElementCollection)parent, _backend);
        }

        ISemanticTag IElementFactory.CreateSemanticTag(IElement parent)
        {
            return new SemanticTag((ParseElementCollection)parent, _backend);
        }

        IElementText IElementFactory.CreateText(IElement parent, string value)
        {
            return null!;
        }

        IToken IElementFactory.CreateToken(IElement parent, string content, string? pronunciation, string? display, float reqConfidence)
        {
            ParseToken((ParseElementCollection)parent, content, pronunciation, display, reqConfidence);
            return null!;
        }

        IItem IElementFactory.CreateItem(IElement? parent, IRule rule, int minRepeat, int maxRepeat, float repeatProbability, float weight)
        {
            return new Item(_backend, (Rule)rule, minRepeat, maxRepeat, repeatProbability, weight);
        }

        IRuleRef IElementFactory.CreateRuleRef(IElement parent, Uri srgsUri)
        {
            throw new NotImplementedException();
        }

        IRuleRef IElementFactory.CreateRuleRef(IElement parent, Uri srgsUri, string? semanticKey, string? parameters)
        {
            return new RuleRef((ParseElementCollection)parent, _backend, srgsUri, _grammar.UndefRules, semanticKey, parameters);
        }

        void IElementFactory.InitSpecialRuleRef(IElement parent, IRuleRef specialRule)
        {
            ((RuleRef)specialRule).InitSpecialRuleRef(_backend, (ParseElementCollection)parent);
        }

        IOneOf IElementFactory.CreateOneOf(IElement? parent, IRule rule)
        {
            return new OneOf((Rule)rule, _backend);
        }

        ISubset IElementFactory.CreateSubset(IElement parent, string text, MatchMode mode)
        {
            return new Subset((ParseElementCollection)parent, _backend, text, mode);
        }

        void IElementFactory.AddScript(IGrammar grammar, string rule, string code)
        {
            ((GrammarElement)grammar).AddScript(rule, code);
        }

        string IElementFactory.AddScript(IGrammar grammar, string rule, string code, string? filename, int line)
        {
            // add the #line information
            if (line >= 0)
            {
                if (_cg._language == "C#")
                {
                    // C#
                    return string.Format(CultureInfo.InvariantCulture, "#line {0} \"{1}\"\n{2}", line.ToString(CultureInfo.InvariantCulture), filename, code);
                }
                else
                {
                    // VB.Net
                    return string.Format(CultureInfo.InvariantCulture, "#ExternalSource (\"{1}\",{0}) \n{2}\n#End ExternalSource\n", line.ToString(CultureInfo.InvariantCulture), filename, code);
                }
            }
            return code;
        }

        void IElementFactory.AddScript(IGrammar grammar, string script, string? filename, int line)
        {
            // add the #line information
            if (line >= 0)
            {
                if (_cg._language == "C#")
                {
                    // C#
                    _cg._script.Append("#line ");
                    _cg._script.Append(line.ToString(CultureInfo.InvariantCulture));
                    _cg._script.Append(" \"");
                    _cg._script.Append(filename);
                    _cg._script.Append("\"\n");
                    _cg._script.Append(script);
                }
                else
                {
                    // VB.Net
                    _cg._script.Append("#ExternalSource (");
                    _cg._script.Append(" \"");
                    _cg._script.Append(filename);
                    _cg._script.Append("\",");
                    _cg._script.Append(line.ToString(CultureInfo.InvariantCulture));
                    _cg._script.Append(")\n");
                    _cg._script.Append(script);
                    _cg._script.Append("#End #ExternalSource\n");
                }
            }
            else
            {
                _cg._script.Append(script);
            }
        }

        void IElementFactory.AddItem(IOneOf oneOf, IItem item)
        {
        }

        void IElementFactory.AddElement(IRule rule, IElement value)
        {
        }

        void IElementFactory.AddElement(IItem item, IElement value)
        {
        }

        #endregion

        #region Internal Properties

        IGrammar IElementFactory.Grammar
        {
            get
            {
                return _grammar;
            }
        }

        IRuleRef IElementFactory.Null
        {
            get
            {
                return RuleRef.Null;
            }
        }
        IRuleRef IElementFactory.Void
        {
            get
            {
                return RuleRef.Void;
            }
        }
        IRuleRef IElementFactory.Garbage
        {
            get
            {
                return RuleRef.Garbage;
            }
        }
        #endregion

        #region Private Methods

        // Disable parameter validation check

        /// <summary>
        /// Add transition representing the normalized token.
        ///
        /// White Space Normalization - Trim leading/trailing white spaces.
        ///                             Collapse white space sequences to a single ' '.
        /// Restrictions - Normalized token cannot be empty.
        ///                Normalized token cannot contain double-quote.
        ///
        /// If (Parent == Token) And (Parent.SAPIPron.Length > 0) Then
        ///     Escape normalized token.  "/" -> "\/", "\" -> "\\"
        ///     Build /D/L/P; form from the escaped token and SAPIPron.
        ///
        /// SAPIPron may be a semi-colon delimited list of pronunciations.
        /// In this case, a transition for each of the pronunciations will be added.
        ///
        /// AddTransition(NormalizedToken, Parent.EndState, NewState)
        /// Parent.EndState = NewState
        /// </summary>
        private void ParseToken(ParseElementCollection parent, string sToken, string? pronunciation, string? display, float reqConfidence)
        {
            int requiredConfidence = (parent != null) ? parent._confidence : CfgGrammar.SP_NORMAL_CONFIDENCE;

            // Performs white space normalization in place
            sToken = Backend.NormalizeTokenWhiteSpace(sToken);
            if (string.IsNullOrEmpty(sToken))
            {
                return;
            }

            System.Diagnostics.Debug.Assert(parent is not null);

            // "sapi:reqconf" Attribute
            parent._confidence = CfgGrammar.SP_NORMAL_CONFIDENCE;  // Default to normal

            if (reqConfidence < 0 || reqConfidence.Equals(0.5f))
            {
                parent._confidence = CfgGrammar.SP_NORMAL_CONFIDENCE;  // Default to normal
            }
            else if (reqConfidence < 0.5)
            {
                parent._confidence = CfgGrammar.SP_LOW_CONFIDENCE;
            }
            else
            {
                parent._confidence = CfgGrammar.SP_HIGH_CONFIDENCE;
            }

            // If SAPIPron is specified, use /D/L/P; as the transition text, for each of the pronunciations.
            if (pronunciation != null || display != null)
            {
                // Escape normalized token.  "/" -> "\/", "\" -> "\\"
                string sEscapedToken = EscapeToken(sToken);
                string sDisplayToken = display == null ? sEscapedToken : EscapeToken(display);

                if (pronunciation != null)
                {
                    // Garbage transition is optional whereas Wildcard is not.  So we need additional epsilon transition.
                    OneOf? oneOf = pronunciation.Contains(';') ? new OneOf(parent._rule, _backend) : null;

                    for (int iCurPron = 0, iDeliminator = 0; iCurPron < pronunciation.Length; iCurPron = iDeliminator + 1)
                    {
                        // Find semi-colon delimiter and replace with null
                        iDeliminator = pronunciation.IndexOf(';', iCurPron);
                        if (iDeliminator == -1)
                        {
                            iDeliminator = pronunciation.Length;
                        }

                        string pron = pronunciation.Substring(iCurPron, iDeliminator - iCurPron);
                        string? sSubPron = null;
                        switch (_backend.Alphabet)
                        {
                            case AlphabetType.Sapi:
                                sSubPron = PhonemeConverter.ConvertPronToId(pron, _grammar.Backend.LangId);
                                break;

                            case AlphabetType.Ipa:
                                sSubPron = pron;
                                PhonemeConverter.ValidateUpsIds(sSubPron);
                                break;

                            case AlphabetType.Ups:
                                sSubPron = PhonemeConverter.UpsConverter.ConvertPronToId(pron);
                                break;
                        }

                        // Build /D/L/P; form for this pronunciation.
                        string sDLP = string.Format(CultureInfo.InvariantCulture, "/{0}/{1}/{2};", sDisplayToken, sEscapedToken, sSubPron);

                        // Add /D/L/P; transition to the new state.
                        if (oneOf != null)
                        {
                            oneOf.AddArc(_backend.WordTransition(sDLP, 1.0f, requiredConfidence));
                        }
                        else
                        {
                            parent.AddArc(_backend.WordTransition(sDLP, 1.0f, requiredConfidence));
                        }
                    }

                    if (oneOf != null)
                    {
                        ((IOneOf)oneOf).PostParse(parent);
                    }
                }
                else
                {
                    // Build /D/L; form for this pronunciation.
                    string sDLP = string.Format(CultureInfo.InvariantCulture, "/{0}/{1};", sDisplayToken, sEscapedToken);

                    // Add /D/L; transition to the new state.
                    parent.AddArc(_backend.WordTransition(sDLP, 1.0f, requiredConfidence));
                }
            }
            else
            {
                // Add transition to the new state with normalized token.
                parent.AddArc(_backend.WordTransition(sToken, 1.0f, requiredConfidence));
            }
        }

        /// <summary>
        /// Escape token.  "/" -> "\/", "\" -> "\\"
        /// </summary>
        private static string EscapeToken(string sToken)                     // String to escape
        {
            System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(sToken));

            // Easy out if no escape characters
            if (!sToken.Contains("\\/", StringComparison.Ordinal))
            {
                return sToken;
            }

            char[] achSrc = sToken.ToCharArray();
            char[] achDest = new char[achSrc.Length * 2];
            int iDest = 0;

            // Escape slashes and backslashes.
            for (int i = 0; i < achSrc.Length;)
            {
                if ((achSrc[i] == '\\') || (achSrc[i] == '/'))
                {
                    achDest[iDest++] = '\\';                            // Escape special character
                }

                achDest[iDest++] = achSrc[i++];
            }

            // null terminate and update string length
            return new string(achDest, 0, iDest);
        }

        #endregion

        #region Private Fields

        // Callers param
        private Backend _backend;

        // Grammar
        private GrammarElement _grammar;

        // Callers param
        private CustomGrammar _cg;

        #endregion
    }
}