venerdì 26 aprile 2013

WP8: le Speech API e le eccezioni – Speech Exceptions Toolkit

Tutti coloro che si stanno cimentando con l’utilizzo delle Speech API di Windows Phone 8, avranno notato, probailmente con qualche improperio, che le eccezioni generate dalle classi utilizzate non sono sempre “parlanti”.

Per fare un esempio, proviamo a utilizzare un Voice Command Definition file (per un’ottima spiegazione riguardo l’utilizzo dei VCD vi rimando ai posts Michele Locuratolo) non correttamente formato.

Potremmo, ad esempio, eliminare il CommandName in un Command del CommandSet:

  1. <?xml version="1.0" encoding="utf-8"?>
  2.  
  3. <VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.0">
  4.   <CommandSet xml:lang="en-US">
  5.     <CommandPrefix>Contoso Rodeo</CommandPrefix>
  6.     <Example> play a new game </Example>
  7.  
  8.     <Command >
  9.       <Example> play a new game </Example>
  10.       <ListenFor> [and] play [a] new game </ListenFor>
  11.       <ListenFor> [and] start [a] new game </ListenFor>
  12.       <Feedback> Starting a new game... </Feedback>
  13.       <Navigate />
  14.     </Command>
  15.  
  16.     <PhraseList Label="number">
  17.       <Item> one </Item>
  18.       <Item> two </Item>
  19.       <Item> three </Item>
  20.     </PhraseList>
  21.  
  22.   </CommandSet>
  23. </VoiceCommands>

In questo caso, nel momento in cui cerchiamo di installare il file tramite il VoiceCommandService otteniamo:

image

Come possiamo vedere, l’eccezione che otteniamo è una System.Exception e, per capire di cosa si tratta, dobbiamo prendere l’HResult (in questo caso 0x80045560) e, armati di santa pazienza e della funzione Search di Internet Explorer, andare alla pagina MSDN “Handling errors in speech apps for Windows Phone” e scoprire che:

image

In alcuni casi siamo fortunati e abbiamo delle eccezioni specifiche ma nella stragrande maggioranza dei casi siamo di fronte ad delle System.Exception con il solo HResult.

I codici di errore disponibili (SPERR_VC_INVALID_COMMAND_ATTRIBUTES in questo caso) sono un centinaio e sarebbe carino poter avere, all’interno dela nostra app, delle costanti che ci permettano di eseguire facilmente il test per capire cosa si è verificato.

Per fare questo possiamo utilizzare la semplice classe:

  1. Public NotInheritable Class SpeechHResults
  2.     Private Sub New()
  3.  
  4.     End Sub
  5.  
  6.     Public Const SPERR_RECOERROR_NOSPEECH As Integer = &H80045501
  7.     Public Const SPERR_RECOERROR_NOMATCH As Integer = &H80045502
  8.     Public Const SPERR_RECOERROR_NETWORK_TIMEOUT As Integer = &H80045503
  9.     Public Const SPERR_RECOERROR_NETWORK_UNAVAILABLE As Integer = &H80045504
  10.     Public Const SPERR_GRAMMAR_DUP_NAME As Integer = &H80045505
  11.     Public Const SPERR_GRAMMARSET_CANT_ADD As Integer = &H80045506
  12.     Public Const SPERR_GRAMMARSET_LOAD_CANCELED As Integer = &H80045507
  13.     Public Const SPERR_SYSTEM_CALL_INTERRUPTED As Integer = &H80045508
  14.     Public Const SPERR_SPEECH_PRIVACY_POLICY_NOT_ACCEPTED As Integer = &H80045509
  15.     Public Const SPERR_AUDIO_LIMIT_EXCEEDED As Integer = &H8004550A
  16.     Public Const SPERR_NO_RULES_TO_ACTIVATE As Integer = &H8004550B
  17.     Public Const SPERR_WINRT_INTERNAL_ERROR As Integer = &H800455A0
  18.     Public Const SPERR_WINRT_ALREADY_IN_LEX As Integer = &H800455A1
  19.     Public Const SPERR_WINRT_NOT_IN_LEX As Integer = &H800455A2
  20.     Public Const SPERR_WINRT_RULE_NOT_DYNAMIC As Integer = &H800455A3
  21.     Public Const SPERR_WINRT_DUPLICATE_RULE_NAME As Integer = &H800455A4
  22.     Public Const SPERR_WINRT_DUPLICATE_RESOURCE_NAME As Integer = &H800455A5
  23.     Public Const SPERR_WINRT_TOO_MANY_GRAMMARS As Integer = &H800455A6
  24.     Public Const SPERR_WINRT_CIRCULAR_REFERENCE As Integer = &H800455A7
  25.     Public Const SPERR_WINRT_INVALID_IMPORT As Integer = &H800455A8
  26.     Public Const SPERR_WINRT_RULE_NAME_ID_CONFLICT As Integer = &H800455A9
  27.     Public Const SPERR_WINRT_NO_RULES As Integer = &H800455AA
  28.     Public Const SPERR_WINRT_CIRCULAR_RULE_REF As Integer = &H800455AB
  29.     Public Const SPERR_WINRT_NOT_DYNAMIC_GRAMMAR As Integer = &H800455AC
  30.     Public Const SPERR_WINRT_AMBIGUOUS_PROPERTY As Integer = &H800455AD
  31.     Public Const SPERR_WINRT_EXPORT_DYNAMIC_RULE As Integer = &H800455AE
  32.     Public Const SPERR_WINRT_WORDFORMAT_ERROR As Integer = &H800455AF
  33.     Public Const SPERR_WINRT_LANGID_MISMATCH As Integer = &H800455B1
  34.     Public Const SPERR_WINRT_NO_WORD_PRONUNCIATION As Integer = &H800455B2
  35.     Public Const SPERR_WINRT_SML_GENERATION_FAIL As Integer = &H800455B3
  36.     Public Const SPERR_WINRT_ROOTRULE_ALREADY_DEFINED As Integer = &H800455B4
  37.     Public Const SPERR_WINRT_UNSUPPORTED_PHONEME As Integer = &H800455B5
  38.     Public Const SPERR_WINRT_PHONEME_CONVERSION As Integer = &H800455B6
  39.     Public Const SPERR_WINRT_NO_RULES_TO_ACTIVATE As Integer = &H800455B7
  40.     Public Const SPERR_WINRT_LEX_INVALID_DATA As Integer = &H800455B8
  41.     Public Const SPERR_WINRT_CFG_INVALID_DATA As Integer = &H800455B9
  42.     Public Const SPERR_WINRT_SISR_ATTRIBUTES_NOT_ALLOWED As Integer = &H800455BA
  43.     Public Const SPERR_WINRT_SISR_MIXED_NOT_ALLOWED As Integer = &H800455BB
  44.     Public Const SPERR_WINRT_UNSUPPORTED_LANG As Integer = &H800455BC
  45.     Public Const SPERR_WINRT_STRING_TOO_LONG As Integer = &H800455BD
  46.     Public Const SPERR_WINRT_STRING_EMPTY As Integer = &H800455BE
  47.     Public Const SPERR_WINRT_NO_MORE_ITEMS As Integer = &H800455BF
  48.     Public Const SPERR_VC_EXPECTED_VOICE_COMMANDS_ELEMENT As Integer = &H80045550
  49.     Public Const SPERR_VC_INVALID_VOICE_COMMANDS_ELEMENT As Integer = &H80045551
  50.     Public Const SPERR_VC_EXPECTED_XML_DECLARATION As Integer = &H80045552
  51.     Public Const SPERR_VC_INVALID_XML_ATTRIBUTES As Integer = &H80045553
  52.     Public Const SPERR_VC_INVALID_XML_VERSION As Integer = &H80045554
  53.     Public Const SPERR_VC_INVALID_XML_ENCODING As Integer = &H80045555
  54.     Public Const SPERR_VC_INVALID_NAMESPACE As Integer = &H80045556
  55.     Public Const SPERR_VC_EXPECTED_COMMANDSET As Integer = &H80045557
  56.     Public Const SPERR_VC_INVALID_COMMANDSET As Integer = &H80045558
  57.     Public Const SPERR_VC_INVALID_COMMANDSET_ATTRIBUTES As Integer = &H80045559
  58.     Public Const SPERR_VC_EXPECTED_PREFIX_OR_EXAMPLE As Integer = &H8004555A
  59.     Public Const SPERR_VC_INVALID_PREFIX As Integer = &H8004555B
  60.     Public Const SPERR_VC_EXPECTED_COMMANDSET_EXAMPLE As Integer = &H8004555C
  61.     Public Const SPERR_VC_INVALID_COMMANDSET_EXAMPLE As Integer = &H8004555D
  62.     Public Const SPERR_VC_EXPECTED_COMMAND As Integer = &H8004555E
  63.     Public Const SPERR_VC_INVALID_COMMAND As Integer = &H8004555F
  64.     Public Const SPERR_VC_INVALID_COMMAND_ATTRIBUTES As Integer = &H80045560
  65.     Public Const SPERR_VC_EXPECTED_COMMAND_OR_PHRASELIST As Integer = &H80045561
  66.     Public Const SPERR_VC_EXPECTED_COMMAND_EXAMPLE As Integer = &H80045562
  67.     Public Const SPERR_VC_INVALID_COMMAND_EXAMPLE As Integer = &H80045563
  68.     Public Const SPERR_VC_EXPECTED_LISTENFOR As Integer = &H80045564
  69.     Public Const SPERR_VC_INVALID_LISTENFOR As Integer = &H80045565
  70.     Public Const SPERR_VC_EXPECTED_FEEDBACK As Integer = &H80045566
  71.     Public Const SPERR_VC_INVALID_FEEDBACK As Integer = &H80045567
  72.     Public Const SPERR_VC_EXPECTED_NAVIGATE As Integer = &H80045568
  73.     Public Const SPERR_VC_INVALID_NAVIGATE As Integer = &H80045569
  74.     Public Const SPERR_VC_INVALID_NAVIGATE_ATTRIBUTES As Integer = &H8004556A
  75.     Public Const SPERR_VC_EXPECTED_PHRASELIST As Integer = &H8004556B
  76.     Public Const SPERR_VC_INVALID_PHRASELIST As Integer = &H8004556C
  77.     Public Const SPERR_VC_INVALID_PHRASELIST_ATTRIBUTES As Integer = &H8004556D
  78.     Public Const SPERR_VC_EXPECTED_PHRASELIST_ITEM As Integer = &H8004556E
  79.     Public Const SPERR_VC_INVALID_PHRASELIST_ITEM As Integer = &H8004556F
  80.     Public Const SPERR_VC_TOO_MANY_PHRASELIST_ITEMS As Integer = &H80045570
  81.     Public Const SPERR_VC_INVALID_NODE As Integer = &H80045571
  82.     Public Const SPERR_VC_TOO_MANY_PHRASELISTS As Integer = &H80045572
  83.     Public Const SPERR_VC_UNEXPECTED_END_OF_FILE As Integer = &H80045573
  84.     Public Const SPERR_VC_EXPECTED_END_OF_FILE As Integer = &H80045574
  85.     Public Const SPERR_VC_INVALID_STRING_LENGTH As Integer = &H80045575
  86.     Public Const SPERR_VC_INVALID_XML_DOCUMENT As Integer = &H80045576
  87.     Public Const SPERR_VC_INVALID_FEEDBACK_LABEL_NO_SEMANTICS As Integer = &H80045577
  88.     Public Const SPERR_VC_INVALID_PATTERN_REFERENCED_LABEL_NOT_FOUND As Integer = &H80045578
  89.     Public Const SPERR_VC_INVALID_PHRASELIST_NEVER_USED As Integer = &H80045579
  90.     Public Const SPERR_VC_INVALID_FEEDBACK_LABEL_NOT_IN_ALL_LISTENFORS As Integer = &H80045580
  91.     Public Const SPERR_VC_TOO_MANY_LISTENFORS As Integer = &H80045581
  92.     Public Const SPERR_VC_TOO_MANY_COMMANDSETS As Integer = &H80045582
  93.     Public Const SPERR_VC_TOO_MANY_COMMANDS As Integer = &H80045583
  94.     Public Const SPERR_VC_INVALID_COMMANDSET_LANGUAGE As Integer = &H80045584
  95.     Public Const SPERR_VC_DUPLICATE_COMMANDSET_LANGUAGE As Integer = &H80045585
  96.  
  97.  
  98.     Public Shared Function GetCodeByHResult(hresult As Integer) As String
  99.         Dim meType = GetType(SpeechHResults)
  100.  
  101.         Dim fieldName = (From f In meType.GetFields(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
  102.                     Where f.FieldType Is GetType(System.Int32) _
  103.                     AndAlso CType(f.GetValue(Nothing), Integer) = hresult
  104.                     Select f.Name).FirstOrDefault()
  105.         Return fieldName
  106.     End Function
  107.  
  108. End Class

Utilizzando questa classe possiamo scrivere:

  1. Private Async Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
  2.     Try
  3.         Await VoiceCommandService.InstallCommandSetsFromFileAsync(New Uri("ms-appx:///VoiceCommandDefinition.xml",
  4.                                                                           UriKind.RelativeOrAbsolute))
  5.     Catch ex As Exception
  6.         If ex.HResult = SpeechHResults.SPERR_VC_INVALID_COMMAND_ATTRIBUTES Then
  7.             MessageBox.Show("The Command element does not specify a Name attribute; or, any attribute within the Command element is invalid.")
  8.         End If
  9.  
  10.     End Try
  11. End Sub

E’ evidente che l’ideale sarebbe avere un toolkit che, a partire dall’eccezione generica generasse un eccezione specifica che contenesse il codice di errore, l’HResult e il messaggio che abbiamo nella sopra citata pagina (magari anche tradotto nella lingua dell’app).

Esiste un piccolissimo toolkit da me realizzato (sono veramente 4 classi in croce), chiamato Speech Exception Toolkit e che potete trovare all’indirizzo https://set.codeplex.com/.

Attualmente supporta le descrizioni delle eccezioni (tradotte automaticamente con il Multilingual App Toolkit di Microsoft) in diverse lingue tra cui l’italiano.

E’ possibile utilizzare NuGet per referenziare il package: https://www.nuget.org/packages/SpeechExceptionsToolkit/

Grazie a questo stupidissimo toolkit possiamo scrivere:

  1. Private Async Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
  2.     Try
  3.         Await VoiceCommandService.InstallCommandSetsFromFileAsync(New Uri("ms-appx:///VoiceCommandDefinition.xml",
  4.                                                                           UriKind.RelativeOrAbsolute))
  5.     Catch ex As Exception
  6.         Dim speechException = ExceptionFactory.CreateException(ex)
  7.         MessageBox.Show(speechException.Message)
  8.     End Try
  9. End Sub

La ExceptionFactory permette di creare, a partire dall’eccezione generica, una eccezione tipizzata che contiene tutte le informazioni utili a capire cosa è successo. La proprietà Message contiene la descrizione (quella presente alla pagina MSDN) tradotta nella lingua del thread corrente.

Se l’eccezione non è un eccezione relativa alle Spech API, l’ExceptionFactory la lascia inalterata.

Il toolkit è distribuito con licenza Ms-PL, quindi potete utilizzarlo senza alcun problema e modificarlo come volete.

 

Nessun commento: