|
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Speech.Internal.SapiInterop;
using System.Speech.Internal.SrgsCompiler;
using System.Speech.Internal.SrgsParser;
using System.Text;
using System.Xml;
using System.Xml.XPath;
namespace System.Speech.Recognition
{
[Serializable]
[DebuggerDisplay("{Text}")]
public class RecognizedPhrase
{
#region Constructors
internal RecognizedPhrase()
{
}
#endregion
#region Public Methods
[MemberNotNull(nameof(_smlContent))]
public IXPathNavigable ConstructSmlFromSemantics()
{
if (!string.IsNullOrEmpty(_smlContent))
{
XmlDocument smlDocument = new();
smlDocument.LoadXml(_smlContent);
return smlDocument;
}
if (_serializedPhrase.SemanticErrorInfoOffset != 0)
{
ThrowInvalidSemanticInterpretationError();
}
XmlDocument document = new();
XmlElement root = document.CreateElement("SML");
NumberFormatInfo nfo = new();
nfo.NumberDecimalDigits = 3;
document.AppendChild(root);
root.SetAttribute("text", Text);
root.SetAttribute("utteranceConfidence", Confidence.ToString("f", nfo));
root.SetAttribute("confidence", Confidence.ToString("f", nfo));
if (Semantics?.Count > 0)
{
AppendPropertiesSML(document, root, Semantics, nfo);
}
else if (Semantics?.Value != null)
{
XmlText valueText = document.CreateTextNode(Semantics.Value.ToString());
root.AppendChild(valueText);
}
// now append the alternates
for (int i = 0; i < _recoResult.Alternates.Count; i++)
{
RecognizedPhrase alternate = _recoResult.Alternates[i];
alternate.AppendSml(document, i + 1, nfo);
}
_smlContent = document.OuterXml;
return document;
}
#endregion
#region Public Properties
public string Text
{
get
{
if (_text == null)
{
Collection<ReplacementText> replacements = ReplacementWordUnits;
ReplacementText? replacement;
int iCurReplacementIndex = 0;
int iWordReplacement = NextReplacementWord(replacements, out replacement, ref iCurReplacementIndex);
StringBuilder sb = new();
for (int i = 0; i < Words.Count; i++)
{
DisplayAttributes displayAttribute;
string? text;
if (i == iWordReplacement)
{
System.Diagnostics.Debug.Assert(replacement != null, "Non-negative iWordReplacement should yield a non-null replacement");
displayAttribute = replacement.DisplayAttributes;
text = replacement.Text;
i += replacement.CountOfWords - 1;
iWordReplacement = NextReplacementWord(replacements, out replacement, ref iCurReplacementIndex);
}
else
{
displayAttribute = Words[i].DisplayAttributes;
text = Words[i].Text;
}
// Remove leading spaces
if ((displayAttribute & DisplayAttributes.ConsumeLeadingSpaces) != 0)
{
while (sb.Length > 0 && sb[sb.Length - 1] == ' ')
{
sb.Remove(sb.Length - 1, 1);
}
}
// Append text
sb.Append(text);
// Add trailing spaces
if ((displayAttribute & DisplayAttributes.ZeroTrailingSpaces) != 0)
{
// no action
}
else if ((displayAttribute & DisplayAttributes.OneTrailingSpace) != 0)
{
sb.Append(' ');
}
else if ((displayAttribute & DisplayAttributes.TwoTrailingSpaces) != 0)
{
sb.Append(" ");
}
}
_text = sb.ToString().Trim(' ');
}
return _text;
}
}
public float Confidence
{
get
{
return _confidence;
}
}
public ReadOnlyCollection<RecognizedWordUnit> Words
{
get
{
if (_words == null)
{
int countOfElements = (int)_serializedPhrase.Rule.ulCountOfElements;
int elementsOffset = (int)_serializedPhrase.ElementsOffset;
List<RecognizedWordUnit> wordList = new(countOfElements);
int sizeofPhraseElement = Marshal.SizeOf<SPSERIALIZEDPHRASEELEMENT>();
GCHandle gc = GCHandle.Alloc(_phraseBuffer, GCHandleType.Pinned);
try
{
IntPtr buffer = gc.AddrOfPinnedObject();
for (int i = 0; i < countOfElements; i++)
{
IntPtr elementBuffer = new((long)buffer + elementsOffset + i * sizeofPhraseElement);
SPSERIALIZEDPHRASEELEMENT element = Marshal.PtrToStructure<SPSERIALIZEDPHRASEELEMENT>(elementBuffer)!;
string? displayForm = null, lexicalForm = null, pronunciation = null;
if (element.pszDisplayTextOffset != 0)
{
IntPtr displayFormBuffer = new((long)buffer + (int)element.pszDisplayTextOffset);
displayForm = Marshal.PtrToStringUni(displayFormBuffer);
}
if (element.pszLexicalFormOffset != 0)
{
IntPtr lexicalFormBuffer = new((long)buffer + (int)element.pszLexicalFormOffset);
lexicalForm = Marshal.PtrToStringUni(lexicalFormBuffer);
}
if (element.pszPronunciationOffset != 0)
{
IntPtr pronunciationBuffer = new((long)buffer + (int)element.pszPronunciationOffset);
pronunciation = Marshal.PtrToStringUni(pronunciationBuffer)!;
if (!_hasIPAPronunciation)
{
pronunciation = _recoResult.ConvertPronunciation(pronunciation, _serializedPhrase.LangID);
}
}
DisplayAttributes displayAttributes = RecognizedWordUnit.SapiAttributesToDisplayAttributes(element.bDisplayAttributes);
// On SAPI 5.1, the engine confidence is not set. Force a value in this case
if (!_isSapi53Header)
{
element.SREngineConfidence = 1.0f;
}
wordList.Add(new RecognizedWordUnit(displayForm, element.SREngineConfidence, pronunciation, lexicalForm!, displayAttributes, new TimeSpan(element.ulAudioTimeOffset * TimeSpan.TicksPerMillisecond / 10000), new TimeSpan(element.ulAudioSizeTime * TimeSpan.TicksPerMillisecond / 10000)));
}
_words = new ReadOnlyCollection<RecognizedWordUnit>(wordList);
}
finally
{
gc.Free();
}
}
return _words;
}
}
// Semantic data about result:
public SemanticValue? Semantics
{
get
{
if (_serializedPhrase.SemanticErrorInfoOffset != 0)
{
ThrowInvalidSemanticInterpretationError();
}
if (_phraseBuffer == null)
{
throw new NotSupportedException();
}
if (_semantics == null)
{
CalcSemantics(Grammar);
}
return _semantics;
}
}
// Homophonic alternates for this phrase
public ReadOnlyCollection<RecognizedPhrase> Homophones
{
get
{
if (_phraseBuffer == null)
{
throw new NotSupportedException();
}
if (_homophones == null)
{
// Walks the list of alternates and collects all phrases that have the same
// homophoneGroupId at the phrase
List<RecognizedPhrase> homophones = new(_recoResult.Alternates.Count);
for (int i = 0; i < _recoResult.Alternates.Count; i++)
{
if ((_recoResult.Alternates[i]._homophoneGroupId == _homophoneGroupId) && (_recoResult.Alternates[i].Text != this.Text))
{
homophones.Add(_recoResult.Alternates[i]);
}
}
_homophones = new ReadOnlyCollection<RecognizedPhrase>(homophones);
}
return _homophones;
}
}
public Grammar? Grammar
{
get
{
// If this phrase comes from a deserialize, then throw
if (_grammarId == unchecked((ulong)(-1)))
{
throw new NotSupportedException(SR.Get(SRID.CantGetPropertyFromSerializedInfo, "Grammar"));
}
if (_grammar == null && _recoResult.Recognizer != null)
{
_grammar = _recoResult.Recognizer.GetGrammarFromId(_grammarId);
}
return _grammar;
}
}
public Collection<ReplacementText> ReplacementWordUnits
{
get
{
if (_replacementText == null)
{
_replacementText = new Collection<ReplacementText>();
GCHandle gc = GCHandle.Alloc(_phraseBuffer, GCHandleType.Pinned);
try
{
IntPtr buffer = gc.AddrOfPinnedObject();
// Get the ITN and Look for replacement phrase/
IntPtr itnBuffer = new((long)buffer + _serializedPhrase.ReplacementsOffset);
for (int i = 0; i < _serializedPhrase.cReplacements; i++, itnBuffer = (nint)itnBuffer + Marshal.SizeOf<SPPHRASEREPLACEMENT>())
{
SPPHRASEREPLACEMENT replacement = Marshal.PtrToStructure<SPPHRASEREPLACEMENT>(itnBuffer)!;
string text = Marshal.PtrToStringUni(new IntPtr((long)buffer + replacement.pszReplacementText))!;
DisplayAttributes displayAttributes = RecognizedWordUnit.SapiAttributesToDisplayAttributes(replacement.bDisplayAttributes);
_replacementText.Add(new ReplacementText(displayAttributes, text, (int)replacement.ulFirstElement, (int)replacement.ulCountOfElements));
}
}
finally
{
gc.Free();
}
}
return _replacementText;
}
}
public int HomophoneGroupId
{
get
{
return _homophoneGroupId;
}
}
#endregion
#region Internal Methods
internal static SPSERIALIZEDPHRASE GetPhraseHeader(IntPtr phraseBuffer, uint expectedPhraseSize, bool isSapi53Header)
{
SPSERIALIZEDPHRASE? serializedPhrase;
if (isSapi53Header)
{
serializedPhrase = Marshal.PtrToStructure<SPSERIALIZEDPHRASE>(phraseBuffer);
}
else
{
SPSERIALIZEDPHRASE_Sapi51? legacyPhrase = Marshal.PtrToStructure<SPSERIALIZEDPHRASE_Sapi51>(phraseBuffer);
System.Diagnostics.Debug.Assert(legacyPhrase != null);
serializedPhrase = new SPSERIALIZEDPHRASE(legacyPhrase);
}
System.Diagnostics.Debug.Assert(serializedPhrase != null);
if (serializedPhrase.ulSerializedSize > expectedPhraseSize)
{
throw new FormatException(SR.Get(SRID.ResultInvalidFormat));
}
return serializedPhrase;
}
internal void InitializeFromSerializedBuffer(RecognitionResult recoResult, SPSERIALIZEDPHRASE serializedPhrase, IntPtr phraseBuffer, int phraseLength, bool isSapi53Header, bool hasIPAPronunciation)
{
_recoResult = recoResult;
_isSapi53Header = isSapi53Header;
_serializedPhrase = serializedPhrase;
_confidence = _serializedPhrase.Rule.SREngineConfidence;
_grammarId = _serializedPhrase.ullGrammarID;
_homophoneGroupId = _serializedPhrase.wHomophoneGroupId;
_hasIPAPronunciation = hasIPAPronunciation;
// Save the phrase blob
_phraseBuffer = new byte[phraseLength];
Marshal.Copy(phraseBuffer, _phraseBuffer, 0, phraseLength);
// Get the grammar options
_grammarOptions = recoResult.Grammar != null ? recoResult.Grammar._semanticTag : GrammarOptions.KeyValuePairSrgs;
// This triggers the semantic processing if any
CalcSemantics(recoResult.Grammar);
}
#endregion
#region Internal Properties
internal ulong GrammarId
{
get
{
return _grammarId;
}
}
internal string SmlContent
{
get
{
if (_smlContent == null)
{
// this method already sets _smlContent
ConstructSmlFromSemantics();
}
return _smlContent;
}
}
#endregion
#region Internal fields
internal SPSERIALIZEDPHRASE _serializedPhrase = null!;
internal byte[]? _phraseBuffer;
internal bool _isSapi53Header;
internal bool _hasIPAPronunciation;
#endregion
#region Private Methods
// Semantic data about result:
private void CalcSemantics(Grammar? grammar)
{
if (_semantics == null && _serializedPhrase.SemanticErrorInfoOffset == 0)
{
GCHandle gc = GCHandle.Alloc(_phraseBuffer, GCHandleType.Pinned);
try
{
IntPtr buffer = gc.AddrOfPinnedObject();
if (!CalcILSemantics(buffer))
{
// List of recognized words
IList<RecognizedWordUnit> words = Words;
// Build the list of rules and property values
RuleNode ruleTree = ExtractRules(grammar, _serializedPhrase.Rule, buffer);
List<ResultPropertiesRef> propertyList = BuildRecoPropertyTree(_serializedPhrase, buffer, ruleTree, words, _isSapi53Header);
// Recursively build the dictionary of properties
_semantics = RecursiveBuildSemanticProperties(words, propertyList, ruleTree, _grammarOptions & GrammarOptions.TagFormat, ref _dupItems);
// Delay the call to TryExecuteOnRecognition until the _semantics has been set
_semantics.Value = TryExecuteOnRecognition(grammar, _recoResult, ruleTree._rule);
}
}
finally
{
gc.Free();
}
}
}
private bool CalcILSemantics(IntPtr phraseBuffer)
{
if ((_grammarOptions & (GrammarOptions.MssV1 | GrammarOptions.W3cV1)) != 0 || _grammarOptions == GrammarOptions.KeyValuePairs)
{
IList<RecognizedWordUnit> words = Words;
_semantics = new SemanticValue("<ROOT>", null, _confidence);
if (_serializedPhrase.PropertiesOffset != 0)
{
RecursivelyExtractSemanticValue(phraseBuffer, (int)_serializedPhrase.PropertiesOffset, _semantics, words, _isSapi53Header, _grammarOptions & GrammarOptions.TagFormat);
}
return true;
}
return false;
}
private static List<ResultPropertiesRef> BuildRecoPropertyTree(SPSERIALIZEDPHRASE serializedPhrase, IntPtr phraseBuffer, RuleNode ruleTree, IList<RecognizedWordUnit> words, bool isSapi53Header)
{
List<ResultPropertiesRef> propertyList = new();
// Array of string containing the rule names.
if ((int)serializedPhrase.PropertiesOffset > 0)
{
RecursivelyExtractSemanticProperties(propertyList, (int)serializedPhrase.PropertiesOffset, phraseBuffer, ruleTree, words, isSapi53Header);
}
return propertyList;
}
private static SemanticValue RecursiveBuildSemanticProperties(IList<RecognizedWordUnit> words, List<ResultPropertiesRef> properties, RuleNode ruleTree, GrammarOptions semanticTag, ref Collection<SemanticValue>? dupItems)
{
SemanticValue semanticValue = new(ruleTree._name, null, ruleTree._confidence);
// Add the semantic values from the child rules
for (RuleNode? children = ruleTree._child; children != null; children = children._next)
{
// Propagate up the semantic values calculated at the children level
SemanticValue childrenSemantics = RecursiveBuildSemanticProperties(words, properties, children, semanticTag, ref dupItems);
if (!children._hasName)
{
foreach (KeyValuePair<string, SemanticValue> kv in childrenSemantics._dictionary)
{
InsertSemanticValueToDictionary(semanticValue, kv.Key, kv.Value, semanticTag, ref dupItems);
}
if (childrenSemantics.Value != null)
{
if ((semanticTag & (GrammarOptions.MssV1 | GrammarOptions.W3cV1)) == 0 && semanticValue._valueFieldSet && !semanticValue.Value!.Equals(childrenSemantics.Value))
{
throw new InvalidOperationException(SR.Get(SRID.DupSemanticValue, ruleTree._name));
}
semanticValue.Value = childrenSemantics.Value;
semanticValue._valueFieldSet = true;
}
}
else
{
// If no value has been set then the recognized text is returned as the value
if (!childrenSemantics._valueFieldSet && childrenSemantics.Count == 0)
{
StringBuilder sb = new();
for (int i = 0; i < children._count; i++)
{
if (sb.Length > 0)
{
sb.Append(' ');
}
sb.Append(words[(int)children._firstElement + i].Text);
}
childrenSemantics._valueFieldSet = true;
childrenSemantics.Value = sb.ToString();
}
semanticValue._dictionary.Add(children._name, childrenSemantics);
}
}
// Add the semantic value from the properties
foreach (ResultPropertiesRef property in properties)
{
if (property._ruleNode == ruleTree)
{
InsertSemanticValueToDictionary(semanticValue, property._name, property._value, semanticTag, ref dupItems);
}
}
Exception? exceptionThrown = null;
// Try to execute the semantic value if OnParse is defined
object? newValue;
bool doneOnParse = TryExecuteOnParse(ruleTree, semanticValue, words, out newValue, ref exceptionThrown);
if (exceptionThrown != null)
{
ExceptionDispatchInfo.Throw(exceptionThrown);
}
//
if (doneOnParse)
{
semanticValue._dictionary.Clear();
semanticValue.Value = newValue;
semanticValue._valueFieldSet = true;
}
return semanticValue;
}
private static void RecursivelyExtractSemanticProperties(List<ResultPropertiesRef> propertyList, int semanticsOffset, IntPtr phraseBuffer, RuleNode ruleTree, IList<RecognizedWordUnit> words, bool isSapi53Header)
{
IntPtr propertyBuffer = new((long)phraseBuffer + semanticsOffset);
SPSERIALIZEDPHRASEPROPERTY property = Marshal.PtrToStructure<SPSERIALIZEDPHRASEPROPERTY>(propertyBuffer)!;
string propertyName;
SemanticValue thisSemanticValue = ExtractSemanticValueInformation(semanticsOffset, property, phraseBuffer, isSapi53Header, out propertyName);
RuleNode node = ruleTree.Find(property.ulFirstElement, property.ulCountOfElements);
if (propertyName == "SemanticKey")
{
node._name = (string)thisSemanticValue.Value!;
node._hasName = true;
}
else
{
propertyList.Add(new ResultPropertiesRef(propertyName, thisSemanticValue, node));
}
if (property.pFirstChildOffset > 0)
{
// add children to the new node
RecursivelyExtractSemanticProperties(propertyList, (int)property.pFirstChildOffset, phraseBuffer, ruleTree, words, isSapi53Header);
}
if (property.pNextSiblingOffset > 0)
{
// add siblings to parent node
RecursivelyExtractSemanticProperties(propertyList, (int)property.pNextSiblingOffset, phraseBuffer, ruleTree, words, isSapi53Header);
}
}
private void RecursivelyExtractSemanticValue(IntPtr phraseBuffer, int semanticsOffset, SemanticValue semanticValue, IList<RecognizedWordUnit> words, bool isSapi53Header, GrammarOptions semanticTag)
{
IntPtr propertyBuffer = new((long)phraseBuffer + semanticsOffset);
SPSERIALIZEDPHRASEPROPERTY property =
Marshal.PtrToStructure<SPSERIALIZEDPHRASEPROPERTY>(propertyBuffer)!;
string propertyName;
SemanticValue thisSemanticValue = ExtractSemanticValueInformation(semanticsOffset, property, phraseBuffer, isSapi53Header, out propertyName);
if (propertyName == "_value" && semanticValue != null)
{
// 'remove' the _value node from the tree by setting its value to the parent's value
// and use the parent as the node to add children (if present)
semanticValue.Value = thisSemanticValue.Value;
if (property.pFirstChildOffset > 0)
{
thisSemanticValue = semanticValue;
}
}
else
{
System.Diagnostics.Debug.Assert(semanticValue != null);
InsertSemanticValueToDictionary(semanticValue, propertyName, thisSemanticValue, semanticTag, ref _dupItems);
}
if (property.pFirstChildOffset > 0)
{
// add children to the new node
RecursivelyExtractSemanticValue(phraseBuffer, (int)property.pFirstChildOffset, thisSemanticValue, words, isSapi53Header, semanticTag);
}
if (property.pNextSiblingOffset > 0)
{
// add siblings to parent node
RecursivelyExtractSemanticValue(phraseBuffer, (int)property.pNextSiblingOffset, semanticValue, words, isSapi53Header, semanticTag);
}
}
private static void InsertSemanticValueToDictionary(SemanticValue semanticValue, string propertyName, SemanticValue thisSemanticValue, GrammarOptions semanticTag, ref Collection<SemanticValue>? dupItems)
{
string key = propertyName;
if ((key == "$" && semanticTag == GrammarOptions.MssV1)
|| (key == "=" && (semanticTag == GrammarOptions.KeyValuePairSrgs || semanticTag == GrammarOptions.KeyValuePairs))
|| (thisSemanticValue.Count == -1 && semanticTag == GrammarOptions.W3cV1))
{
if ((semanticTag & (GrammarOptions.MssV1 | GrammarOptions.W3cV1)) == 0 && semanticValue._valueFieldSet && !semanticValue.Value!.Equals(thisSemanticValue.Value))
{
throw new InvalidOperationException(SR.Get(SRID.DupSemanticValue, semanticValue.KeyName));
}
semanticValue.Value = thisSemanticValue.Value;
semanticValue._valueFieldSet = true;
}
else
{
bool containsKey = semanticValue._dictionary.ContainsKey(key);
if (!containsKey)
{
semanticValue._dictionary.Add(key, thisSemanticValue);
}
else
{
if (!semanticValue._dictionary[key].Equals(thisSemanticValue))
{
// Error out for Srgs grammars
if (semanticTag == GrammarOptions.KeyValuePairSrgs)
{
throw new InvalidOperationException(SR.Get(SRID.DupSemanticKey, propertyName, semanticValue.KeyName));
}
// Append a _* on the key name for none SAPI grammars
int count = 0;
do
{
key = propertyName + string.Format(CultureInfo.InvariantCulture, "_{0}", count++);
}
while (semanticValue._dictionary.ContainsKey(key));
semanticValue._dictionary.Add(key, thisSemanticValue);
dupItems ??= new Collection<SemanticValue>();
SemanticValue s = semanticValue._dictionary[key];
dupItems.Add(s);
}
}
}
}
private static SemanticValue ExtractSemanticValueInformation(int semanticsOffset, SPSERIALIZEDPHRASEPROPERTY property, IntPtr phraseBuffer, bool isSapi53Header, out string propertyName)
{
object? propertyValue;
bool isIdName = false;
if (property.pszNameOffset > 0)
{
IntPtr nameBuffer = new((long)phraseBuffer + (int)property.pszNameOffset);
propertyName = Marshal.PtrToStringUni(nameBuffer)!;
}
else
{
propertyName = property.ulId.ToString(CultureInfo.InvariantCulture);
isIdName = true;
}
if (property.pszValueOffset > 0)
{
IntPtr valueStringBuffer = new((long)phraseBuffer + (int)property.pszValueOffset);
propertyValue = Marshal.PtrToStringUni(valueStringBuffer)!;
if (!isSapi53Header && isIdName && ((string)propertyValue).Contains('$'))
{
// SAPI 5.1 result that contains script fragments rather than output of executing script.
// Strip this information as script-based grammars aren't supported on 5.1.
throw new NotSupportedException(SR.Get(SRID.NotSupportedWithThisVersionOfSAPI));
}
}
else
{
if (property.SpVariantSubset >= 0)
{
IntPtr valueBuffer = new((long)phraseBuffer + +semanticsOffset + SpVariantSubsetOffset);
#pragma warning disable 0618 // VarEnum is obsolete
switch ((VarEnum)property.vValue)
{
case VarEnum.VT_I4:
propertyValue = Marshal.ReadInt32(valueBuffer);
break;
case VarEnum.VT_UI4:
propertyValue = Marshal.PtrToStructure<uint>(valueBuffer);
break;
case VarEnum.VT_I8:
propertyValue = Marshal.ReadInt64(valueBuffer);
break;
case VarEnum.VT_UI8:
propertyValue = Marshal.PtrToStructure<ulong>(valueBuffer);
break;
case VarEnum.VT_R4:
propertyValue = Marshal.PtrToStructure<float>(valueBuffer);
break;
case VarEnum.VT_R8:
propertyValue = Marshal.PtrToStructure<double>(valueBuffer);
break;
case VarEnum.VT_BOOL:
propertyValue = (Marshal.ReadByte(valueBuffer) != 0);
break;
case VarEnum.VT_EMPTY:
propertyValue = null;
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnhandledVariant));
}
#pragma warning restore 0618
}
else
{
propertyValue = string.Empty;
}
}
return new SemanticValue(propertyName, propertyValue, property.SREngineConfidence);
}
private static RuleNode ExtractRules(Grammar? grammar, SPSERIALIZEDPHRASERULE rule, IntPtr phraseBuffer)
{
// Get the rule name
IntPtr nameBuffer = new((long)phraseBuffer + (int)rule.pszNameOffset);
// Add the rule name to the proper element index
string name = Marshal.PtrToStringUni(nameBuffer)!;
// find the grammar for this rule. If the grammar does not belong to any existing ruleref then
// it must be local.
Grammar? ruleRef = grammar?.Find(name);
if (ruleRef != null)
{
grammar = ruleRef;
}
RuleNode node = new(grammar, name, rule.SREngineConfidence, rule.ulFirstElement, rule.ulCountOfElements);
if (rule.NextSiblingOffset > 0)
{
IntPtr elementBuffer = new((long)phraseBuffer + rule.NextSiblingOffset);
SPSERIALIZEDPHRASERULE ruleNext = Marshal.PtrToStructure<SPSERIALIZEDPHRASERULE>(elementBuffer)!;
node._next = ExtractRules(grammar, ruleNext, phraseBuffer);
}
if (rule.FirstChildOffset > 0)
{
IntPtr elementBuffer = new((long)phraseBuffer + rule.FirstChildOffset);
SPSERIALIZEDPHRASERULE ruleFirst = Marshal.PtrToStructure<SPSERIALIZEDPHRASERULE>(elementBuffer)!;
node._child = ExtractRules(grammar, ruleFirst, phraseBuffer);
}
return node;
}
[DoesNotReturn]
private void ThrowInvalidSemanticInterpretationError()
{
string error;
if (!_isSapi53Header)
{
throw new NotSupportedException(SR.Get(SRID.NotSupportedWithThisVersionOfSAPI));
}
GCHandle gc = GCHandle.Alloc(_phraseBuffer, GCHandleType.Pinned);
try
{
IntPtr smlBuffer = gc.AddrOfPinnedObject();
SPSEMANTICERRORINFO semanticError = Marshal.PtrToStructure<SPSEMANTICERRORINFO>((nint)smlBuffer + (nint)_serializedPhrase.SemanticErrorInfoOffset)!;
string? source = Marshal.PtrToStringUni((nint)smlBuffer + (nint)semanticError.pszSourceOffset);
string? description = Marshal.PtrToStringUni((nint)smlBuffer + (nint)semanticError.pszDescriptionOffset);
string? script = Marshal.PtrToStringUni((nint)smlBuffer + (nint)semanticError.pszScriptLineOffset);
error = string.Format(CultureInfo.InvariantCulture, "Error while evaluating semantic interpretation:\n" +
" HRESULT: {0:x}\n" +
" Line: {1}\n" +
" Source: {2}\n" +
" Description: {3}\n" +
" Script: {4}\n", semanticError.hrResultCode, semanticError.ulLineNumber, source, description, script);
}
finally
{
gc.Free();
}
throw new InvalidOperationException(error);
}
private static bool TryExecuteOnParse(RuleNode ruleRef, SemanticValue value, IList<RecognizedWordUnit> words, out object? newValue, ref Exception? exceptionThrown)
{
newValue = null;
bool doneOnParse = false;
Grammar? grammar = ruleRef._grammar;
if (grammar != null && grammar._scripts != null)
{
// Check if the Inner
try
{
if (exceptionThrown == null)
{
doneOnParse = ExecuteOnParse(grammar, ruleRef, value, words, out newValue);
}
else
{
if (ExecuteOnError(grammar, ruleRef, exceptionThrown))
{
exceptionThrown = null;
}
}
}
catch (Exception e)
{
if (exceptionThrown == null)
{
exceptionThrown = e;
// Try to execute on Error on this thread
try
{
if (ExecuteOnError(grammar, ruleRef, exceptionThrown))
{
exceptionThrown = null;
}
}
catch (Exception e2)
{
exceptionThrown = e2;
}
}
}
}
return doneOnParse;
}
private static bool ExecuteOnParse(Grammar grammar, RuleNode ruleRef, SemanticValue value, IList<RecognizedWordUnit> words, out object? newValue)
{
// Get the rule list
ScriptRef[]? scripts = grammar._scripts;
System.Diagnostics.Debug.Assert(scripts != null);
bool doneOnParse = false;
newValue = null;
// Look if an OnParse exist for this method
for (int iScript = 0; iScript < scripts.Length; iScript++)
{
ScriptRef script = scripts[iScript];
if (ruleRef._rule == script._rule)
{
if (script._method == RuleMethodScript.onParse)
{
// Get the method to invoke
RecognizedWordUnit[] recoUnits = new RecognizedWordUnit[ruleRef._count];
for (int i = 0; i < ruleRef._count; i++)
{
recoUnits[i] = words[i];
}
object[] parameters = new object[2] { value, recoUnits };
if (grammar._proxy != null)
{
Exception? appDomainException;
newValue = grammar._proxy.OnParse(script._rule, script._sMethod, parameters, out appDomainException);
if (appDomainException != null)
{
ExceptionDispatchInfo.Throw(appDomainException);
}
}
else
{
MethodInfo? onParse;
System.Speech.Recognition.Grammar? rule;
GetRuleInstance(grammar, script._rule, script._sMethod, out onParse, out rule);
// Execute the parse routine
newValue = onParse.Invoke(rule, parameters);
}
doneOnParse = true;
}
}
}
return doneOnParse;
}
private static bool ExecuteOnError(Grammar grammar, RuleNode ruleRef, Exception e)
{
// Get the rule list
ScriptRef[]? scripts = grammar._scripts;
System.Diagnostics.Debug.Assert(scripts != null);
bool invoked = false;
// Look if an OnParse exist for this method
for (int iScript = 0; iScript < scripts.Length; iScript++)
{
ScriptRef script = scripts[iScript];
if (ruleRef._rule == script._rule)
{
if (script._method == RuleMethodScript.onError)
{
// Get the method to invoke
object[] parameters = new object[] { e };
if (grammar._proxy != null)
{
Exception? appDomainException;
grammar._proxy.OnError(script._rule, script._sMethod, parameters, out appDomainException);
if (appDomainException != null)
{
ExceptionDispatchInfo.Throw(appDomainException);
}
}
else
{
MethodInfo? onError;
System.Speech.Recognition.Grammar? rule;
GetRuleInstance(grammar, script._rule, script._sMethod, out onError, out rule);
// Execute the parse routine
onError.Invoke(rule, parameters);
}
invoked = true;
}
}
}
return invoked;
}
private static object? TryExecuteOnRecognition(Grammar? grammar, RecognitionResult result, string rootRule)
{
object? resultValue = result.Semantics?.Value;
if (grammar != null && grammar._scripts != null)
{
// Get the rule list
ScriptRef[] scripts = grammar._scripts;
// Look if an OnRecognition exist for this method
for (int iScript = 0; iScript < scripts.Length; iScript++)
{
ScriptRef script = scripts[iScript];
if (rootRule == script._rule)
{
if (script._method == RuleMethodScript.onRecognition)
{
// Get the method to invoke
object[] parameters = new object[1] { result };
if (grammar._proxy != null)
{
Exception? appDomainException;
resultValue = grammar._proxy.OnRecognition(script._sMethod, parameters, out appDomainException);
if (appDomainException != null)
{
ExceptionDispatchInfo.Throw(appDomainException);
}
}
else
{
Type grammarType = grammar.GetType();
MethodInfo onRecognition = grammarType.GetMethod(script._sMethod, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
// Execute the parse routine
resultValue = onRecognition.Invoke(grammar, parameters);
}
}
}
}
}
return resultValue;
}
private static void GetRuleInstance(Grammar grammar, string rule, string method, out MethodInfo onParse, out Grammar? ruleInstance)
{
Type grammarType = grammar.GetType();
Assembly assembly = grammarType.Assembly;
Type? ruleClass = rule == grammarType.Name ? grammarType : GetTypeForRule(assembly, rule);
if (ruleClass == null || !ruleClass.IsSubclassOf(typeof(System.Speech.Recognition.Grammar)))
{
throw new FormatException(SR.Get(SRID.RecognizerInvalidBinaryGrammar));
}
ruleInstance = ruleClass == grammarType ? grammar : (System.Speech.Recognition.Grammar)assembly.CreateInstance(ruleClass.FullName!)!;
onParse = ruleInstance.MethodInfo(method)!;
System.Diagnostics.Debug.Assert(onParse != null);
}
private static Type? GetTypeForRule(Assembly assembly, string rule)
{
Type[] types = assembly.GetTypes();
for (int iType = 0; iType < types.Length; iType++)
{
Type type = types[iType];
if (type.Name == rule && type.IsPublic && type.IsSubclassOf(typeof(System.Speech.Recognition.Grammar)))
{
return type;
}
}
return null;
}
private static int NextReplacementWord(Collection<ReplacementText> replacements, out ReplacementText? replacement, ref int posInCollection)
{
if (posInCollection < replacements.Count)
{
replacement = replacements[posInCollection++];
return replacement.FirstWordIndex;
}
else
{
replacement = null;
return -1;
}
}
private void AppendSml(XmlDocument document, int i, NumberFormatInfo nfo)
{
XmlElement? root = document.DocumentElement;
System.Diagnostics.Debug.Assert(root != null, "Document should have had an element added");
XmlElement alternateNode = document.CreateElement("alternate");
root.AppendChild(alternateNode);
alternateNode.SetAttribute("Rank", i.ToString(CultureInfo.CurrentCulture));
alternateNode.SetAttribute("text", Text);
alternateNode.SetAttribute("utteranceConfidence", Confidence.ToString("f", nfo));
alternateNode.SetAttribute("confidence", Confidence.ToString("f", nfo));
System.Diagnostics.Debug.Assert(_semantics != null);
if (_semantics.Value != null)
{
XmlText valueText = document.CreateTextNode(_semantics.Value.ToString());
alternateNode.AppendChild(valueText);
}
// recursively add the properties now
AppendPropertiesSML(document, alternateNode, _semantics, nfo);
}
private void AppendPropertiesSML(XmlDocument document, XmlElement alternateNode, SemanticValue? semanticsNode, NumberFormatInfo nfo)
{
if (semanticsNode != null)
{
foreach (KeyValuePair<string, SemanticValue> kv in semanticsNode)
{
if (kv.Key == "_attributes")
{
// all the attributes are located under the attribute property.
AppendAttributes(alternateNode, kv.Value);
if (string.IsNullOrEmpty(alternateNode.InnerText) && semanticsNode.Value != null)
{
XmlText valueText = document.CreateTextNode(semanticsNode.Value.ToString());
alternateNode.AppendChild(valueText);
}
}
else
{
string keyName = kv.Key;
if (_dupItems != null && _dupItems.Contains(kv.Value))
{
keyName = RemoveTrailingNumber(kv.Key);
}
XmlElement propertyNode = document.CreateElement(keyName);
propertyNode.SetAttribute("confidence", semanticsNode[kv.Key].Confidence.ToString("f", nfo));
alternateNode.AppendChild(propertyNode);
if (kv.Value.Count > 0)
{
if (kv.Value.Value != null)
{
XmlText valueText = document.CreateTextNode(kv.Value.Value.ToString());
propertyNode.AppendChild(valueText);
}
AppendPropertiesSML(document, propertyNode, kv.Value, nfo);
}
else if (kv.Value.Value != null)
{
XmlText valueText = document.CreateTextNode(kv.Value.Value.ToString());
propertyNode.AppendChild(valueText);
}
}
}
}
}
private string RemoveTrailingNumber(string name)
{
return name.Substring(0, name.LastIndexOf('_'));
}
private void AppendAttributes(XmlElement propertyNode, SemanticValue semanticValue)
{
foreach (KeyValuePair<string, SemanticValue> kv in semanticValue)
{
if (propertyNode.Attributes[kv.Key] == null)
{
propertyNode.SetAttribute(kv.Key, kv.Value.Value?.ToString());
}
}
}
#endregion
#region Private Types
[DebuggerDisplay("{DisplayDebugInfo()}")]
private sealed class RuleNode
{
internal RuleNode(Grammar? grammar, string rule, float confidence, uint first, uint count)
{
_rule = _name = rule;
_firstElement = first;
_count = count;
_confidence = confidence;
_grammar = grammar;
//_ruleId = id;
}
/// <summary>
/// Find the rule enclosing a property.
/// </summary>
/// <param name="firstElement">First word matching the property</param>
/// <param name="count">Count of words</param>
internal RuleNode Find(uint firstElement, uint count)
{
// If the count of word is set to zero. It means that the tag is located just before a word.
// The trick here is to use 1/2 position to locate tags in this case.
float firstWord, lastWord;
if (count == 0)
{
firstWord = lastWord = firstElement - 0.5f;
}
else
{
firstWord = firstElement;
lastWord = firstWord + (count - 1);
}
for (RuleNode? child = _child; child != null; child = child._next)
{
float ruleFirstWord, ruleLastWord;
if (child._count == 0)
{
ruleFirstWord = ruleLastWord = child._firstElement - 0.5f;
}
else
{
ruleFirstWord = child._firstElement;
ruleLastWord = ruleFirstWord + (child._count - 1);
}
if (ruleFirstWord <= firstElement && ruleLastWord >= lastWord)
{
return child.Find(firstElement, count);
}
}
return this;
}
private string DisplayDebugInfo()
{
return $"'rule = {_rule}";
}
internal Grammar? _grammar;
internal string _rule;
internal string _name;
internal uint _firstElement;
internal uint _count;
internal float _confidence;
internal bool _hasName;
internal RuleNode? _next;
internal RuleNode? _child;
}
[DebuggerDisplay("Name = {_name}, node = {_ruleNode._rule}, value = {_value != null && _value.Value != null ? _value.Value.ToString() : \"\"}")]
private struct ResultPropertiesRef
{
internal string _name;
internal SemanticValue _value;
internal RuleNode _ruleNode;
internal ResultPropertiesRef(string name, SemanticValue value, RuleNode ruleNode)
{
_name = name;
_value = value;
_ruleNode = ruleNode;
}
}
#endregion
#region Private Fields
private RecognitionResult _recoResult = null!;
private GrammarOptions _grammarOptions;
private string? _text;
private float _confidence;
private SemanticValue? _semantics;
private ReadOnlyCollection<RecognizedWordUnit>? _words;
private Collection<ReplacementText>? _replacementText;
[NonSerializedAttribute]
private ulong _grammarId = unchecked((ulong)(-1));
#pragma warning disable 6524
[NonSerializedAttribute]
private Grammar? _grammar;
#pragma warning restore 6524
private int _homophoneGroupId;
private ReadOnlyCollection<RecognizedPhrase>? _homophones;
private Collection<SemanticValue>? _dupItems;
private string? _smlContent;
private const int SpVariantSubsetOffset = 16;
#endregion
}
}
|