@@ -600,6 +600,14 @@ impl SsmlFormatterBase {
600600 }
601601 attrs
602602 } ) ) ,
603+ "xsampa" | "praat" | "sil" | "branner" => {
604+ let translated = translate_to_ipa ( & key. to_lowercase ( ) , value) ?;
605+ let mut attrs = vec ! [ ( "alphabet" . to_string( ) , "ipa" . to_string( ) ) ] ;
606+ if !translated. is_empty ( ) {
607+ attrs. push ( ( "ph" . to_string ( ) , translated) ) ;
608+ }
609+ Some ( ( "phoneme" . to_string ( ) , attrs) )
610+ }
603611 "sub" => {
604612 if !value. is_empty ( ) {
605613 attributes. push ( ( "alias" . to_string ( ) , value. to_string ( ) ) ) ;
@@ -735,6 +743,33 @@ impl SsmlFormatterBase {
735743 }
736744}
737745
746+ /// Translate a phonetic-alphabet value to IPA.
747+ ///
748+ /// Recognized keys (lowercase): `xsampa`, `praat`, `sil`, `branner`. Returns
749+ /// `None` for any unrecognized key, or for every key when the
750+ /// `phonetic-translation` feature is disabled — callers treat `None` as
751+ /// "drop this modifier."
752+ ///
753+ /// Conversion is infallible (garbage in, garbage out) per the upstream crate.
754+ #[ cfg( feature = "phonetic-translation" ) ]
755+ pub fn translate_to_ipa ( key : & str , value : & str ) -> Option < String > {
756+ if value. is_empty ( ) {
757+ return Some ( String :: new ( ) ) ;
758+ }
759+ match key {
760+ "xsampa" => Some ( ipa_translate:: xsampa_to_ipa ( value) ) ,
761+ "praat" => Some ( ipa_translate:: praat_to_ipa ( value) ) ,
762+ "sil" => Some ( ipa_translate:: sil_to_ipa ( value) ) ,
763+ "branner" => Some ( ipa_translate:: branner_to_ipa ( value) ) ,
764+ _ => None ,
765+ }
766+ }
767+
768+ #[ cfg( not( feature = "phonetic-translation" ) ) ]
769+ pub fn translate_to_ipa ( _key : & str , _value : & str ) -> Option < String > {
770+ None
771+ }
772+
738773pub fn format_attr_string_ordered ( tag_name : & str , attributes : & TagAttrs ) -> String {
739774 let fixed_order: Vec < & str > = match tag_name {
740775 "say-as" => vec ! [ "interpret-as" , "format" ] ,
@@ -789,3 +824,64 @@ impl Formatter for SsmlFormatterBase {
789824 self . format_node_with_tags ( node)
790825 }
791826}
827+
828+ #[ cfg( test) ]
829+ mod phonetic_alphabet_tests {
830+ use super :: * ;
831+ use crate :: formatters:: base:: FormatterOptions ;
832+
833+ fn fmt ( ) -> SsmlFormatterBase {
834+ SsmlFormatterBase :: new ( FormatterOptions :: default ( ) )
835+ }
836+
837+ #[ cfg( feature = "phonetic-translation" ) ]
838+ #[ test]
839+ fn xsampa_value_becomes_ipa_phoneme_tag ( ) {
840+ let ( tag, attrs) = fmt ( ) . attribute_to_tag ( "xsampa" , "spitS" ) . unwrap ( ) ;
841+ assert_eq ! ( tag, "phoneme" ) ;
842+ assert_eq ! ( attrs_get( & attrs, "alphabet" ) , Some ( "ipa" ) ) ;
843+ assert_eq ! ( attrs_get( & attrs, "ph" ) , Some ( "spitʃ" ) ) ;
844+ }
845+
846+ #[ cfg( feature = "phonetic-translation" ) ]
847+ #[ test]
848+ fn praat_sil_branner_all_emit_ipa_phoneme ( ) {
849+ let cases = [
850+ ( "praat" , r"p\rta\:ft\^h" , "pɹaːtʰ" ) ,
851+ ( "sil" , "si=l" , "sɪl" ) ,
852+ ( "branner" , "br&ae):nE&r^" , "bɹæːnɜ˞" ) ,
853+ ] ;
854+ for ( key, src, expected_ipa) in cases {
855+ let ( tag, attrs) = fmt ( ) . attribute_to_tag ( key, src) . unwrap ( ) ;
856+ assert_eq ! ( tag, "phoneme" , "key {}" , key) ;
857+ assert_eq ! ( attrs_get( & attrs, "alphabet" ) , Some ( "ipa" ) , "key {}" , key) ;
858+ assert_eq ! (
859+ attrs_get( & attrs, "ph" ) ,
860+ Some ( expected_ipa) ,
861+ "key {}" ,
862+ key
863+ ) ;
864+ }
865+ }
866+
867+ #[ cfg( not( feature = "phonetic-translation" ) ) ]
868+ #[ test]
869+ fn phonetic_keys_dropped_when_feature_disabled ( ) {
870+ for key in [ "xsampa" , "praat" , "sil" , "branner" ] {
871+ assert ! (
872+ fmt( ) . attribute_to_tag( key, "anything" ) . is_none( ) ,
873+ "{} should be dropped without phonetic-translation feature" ,
874+ key
875+ ) ;
876+ }
877+ }
878+
879+ #[ cfg( feature = "phonetic-translation" ) ]
880+ #[ test]
881+ fn empty_value_emits_phoneme_without_ph_attr ( ) {
882+ let ( tag, attrs) = fmt ( ) . attribute_to_tag ( "xsampa" , "" ) . unwrap ( ) ;
883+ assert_eq ! ( tag, "phoneme" ) ;
884+ assert_eq ! ( attrs_get( & attrs, "alphabet" ) , Some ( "ipa" ) ) ;
885+ assert_eq ! ( attrs_get( & attrs, "ph" ) , None ) ;
886+ }
887+ }
0 commit comments