You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeaccessibility/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl

165 lines
6.7 KiB

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" indent="no"/>
<!-- XSLT stylesheet to convert SSML into a format that can be processed by the
Hadifix txt2pho processor.
(c) 2004 by Gary Cramblitt
Original author: Gary Cramblitt <garycramblitt@comcast.net>
Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net>
This program is free software; you can redistribute it and/or modify *
it under the terms of the GNU General Public License as published by *
the Free Software Foundation; either version 2 of the License, or *
(at your option) any later version. *
The txt2pho processor permits special markup to be embedded in text to control
speech attributes such as speed (duration), pitch, etc.
The markup must be inside curly braces and separated from other text by a space.
Spaces within the markup are not permitted.
See the txt2pho README file for details.
Something the README does not say is that {Pitch} markup applies only to one
sentence and reverts back to normal when the sentence is completed.
It means that we must repeatedly ouput {Pitch} markup
for each sentence in the text. For example, the SSML
<prosody pitch="high">Sentence one. Sentence two.</prosody> Sentence three.
must be converted to
{Pitch:300} Sentence one. {Pitch:300} Sentence two. Sentence three.
{Duration} markup, on the other hand, is addative and stays in effect until
changed. For example, the SSML
<prosody rate="fast">Sentence one. Sentence two.</prosody> Sentence three.
must be converted to
{Duration:-0.5} Sentence one. Sentence two. {Duration:0.5} Sentence three.
txt2pho will also stop processing when it sees a newline. Therefore, we must take
care to strip all newlines and avoid inserting any newlines.
-->
<!-- Strip all elements and attributes from output. -->
<xsl:strip-space elements="*" />
<xsl:template match="speak">
<xsl:apply-templates/>
</xsl:template>
<!-- Handle markup that maintains state. -->
<xsl:template match="prosody">
<!-- Rate (speed), Rates are addative and stay in effect until changed. -->
<!-- TODO: SSML permits nesting of prosody elements. As coded below,
<prosody rate="slow">One<prosody rate="slow">Two</prosody</prosody>
will pronounce "Two" doubly slow, which it should not do. -->
<xsl:choose>
<xsl:when test="@rate='fast'">
<!-- Increase speed. -->
<xsl:value-of select="'{Duration:-0.5} '"/>
<!-- Continue processing. -->
<xsl:apply-templates/>
<!-- Decrease speed. -->
<xsl:value-of select="'{Duration:0.5} '"/>
</xsl:when>
<xsl:when test="@rate='slow'">
<!-- Decrease speed. -->
<xsl:value-of select="'{Duration:0.5} '"/>
<!-- Continue processing. -->
<xsl:apply-templates/>
<!-- Increase speed. -->
<xsl:value-of select="'{Duration:-0.5} '"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Outputs a sentence with txt2pho markup.
Called in the context of a text node. Obtain markup by
extracting ancestor node attributes.
@param sentence The sentence to mark up.
@return Sentence preceeded by txt2pho markup.
-->
<xsl:template name="output-sentence">
<xsl:param name="sentence"/>
<!-- Pitch -->
<xsl:if test="ancestor::prosody/@pitch='high'">
<xsl:value-of select="'{Pitch:300} '"/>
</xsl:if>
<xsl:if test="ancestor::prosody/@pitch='low'">
<xsl:value-of select="'{Pitch:-40} '"/>
</xsl:if>
<!-- Output the sentence itself. -->
<xsl:value-of select="$sentence"/>
</xsl:template>
<!-- Return the first sentence of argument.
Sentence delimiters are '.:;!?'
@param paragraph Paragraph from which to extract first sentence.
@return The first sentence, or if no such sentence, the paragraph.
-->
<xsl:template name="parse-sentence">
<xsl:param name="paragraph"/>
<!-- Copy paragraph, replacing all delimeters with period. -->
<xsl:variable name="tmp">
<xsl:value-of select="translate($paragraph,':;!?','....')"/>
</xsl:variable>
<!-- Look for first period and space and extract corresponding substring from original. -->
<xsl:choose>
<xsl:when test="contains($tmp, '. ')">
<xsl:value-of select="substring($paragraph, 1, string-length(substring-before($tmp, '. '))+2)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$paragraph"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Process a paragraph, outputting each sentence with txt2pho markup.
@param paragraph The paragraph to process.
@return The paragraph with each sentence preceeded by txt2pho markup.
-->
<xsl:template name="output-paragraph">
<xsl:param name="paragraph" />
<!-- Stop when no more sentences to output. -->
<xsl:choose>
<xsl:when test="normalize-space($paragraph)!=''">
<!-- Split the paragraph into first sentence and rest of paragraph (if any). -->
<xsl:variable name="sentence">
<xsl:call-template name="parse-sentence">
<xsl:with-param name="paragraph" select="$paragraph"/>
</xsl:call-template>
</xsl:variable>
<!-- debug: <xsl:value-of select="concat('[sentence: ',$sentence,']')"/> -->
<xsl:variable name="rest">
<xsl:value-of select="substring-after($paragraph,$sentence)" />
</xsl:variable>
<!-- Output the sentence with markup. -->
<xsl:call-template name="output-sentence" >
<xsl:with-param name="sentence" select="$sentence" />
</xsl:call-template>
<!-- Recursively process the rest of the paragraph. -->
<xsl:call-template name="output-paragraph">
<xsl:with-param name="paragraph" select="$rest" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- Process each text node. -->
<xsl:template match="text()">
<!-- debug: <xsl:value-of select="concat('[paragraph: ',normalize-space(.),']')"/> -->
<xsl:call-template name="output-paragraph">
<xsl:with-param name="paragraph" select="concat(normalize-space(.),' ')"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>