001/**
002 * Copyright 2001 Sun Microsystems, Inc.
003 * 
004 * See the file "license.terms" for information on usage and
005 * redistribution of this file, and for a DISCLAIMER OF ALL 
006 * WARRANTIES.
007 */
008package com.sun.speech.engine.synthesis.text;
009
010import javax.speech.Engine;
011import javax.speech.synthesis.Speakable;
012import javax.speech.synthesis.SpeakableEvent;
013
014import com.sun.speech.engine.synthesis.BaseSynthesizer;
015import com.sun.speech.engine.synthesis.BaseSynthesizerQueueItem;
016
017import java.net.URL;
018import java.io.IOException;
019
020import org.w3c.dom.Document;
021import org.w3c.dom.Node;
022import org.w3c.dom.Element;
023import org.w3c.dom.Text;
024
025/**
026 * Represents an object on the speech output queue of a
027 * <code>TextSynthesizer</code>.
028 */
029public class TextSynthesizerQueueItem extends BaseSynthesizerQueueItem {
030    static public final String JSML = "jsml";
031    static public final String[] JSML_ATTRIBUTES = {
032        "lang", "mark"
033    };
034
035    static public final String DIV = "div";
036    static public final String[] DIV_ATTRIBUTES = {
037        "type", "mark"
038    };
039    
040    static public final String VOICE = "voice";
041    static public final String[] VOICE_ATTRIBUTES = {
042        "voice", "gender", "age", "variant", "name", "mark"
043    };
044    
045    static public final String SAYAS = "sayas";
046    static public final String[] SAYAS_ATTRIBUTES = {
047        "class", "mark"
048    };
049
050    static public final String PHONEME = "phoneme";
051    static public final String[] PHONEME_ATTRIBUTES = {
052        "original", "mark"
053    };
054
055    static public final String EMPHASIS = "emphasis";
056    static public final String[] EMPHASIS_ATTRIBUTES = {
057        "level", "mark"
058    };
059
060    static public final String BREAK = "break";
061    static public final String[] BREAK_ATTRIBUTES = {
062        "size", "time", "mark"
063    };
064
065    static public final String PROSODY = "prosody";
066    static public final String[] PROSODY_ATTRIBUTES = {
067        "rate", "volume", "pitch", "range", "mark"
068    };
069
070    static public final String MARKER = "marker";
071    static public final String[] MARKER_ATTRIBUTES = {
072        "mark"
073    };
074    
075    static public final String ENGINE = "engine";
076    static public final String[] ENGINE_ATTRIBUTES = {
077        "name", "data", "mark"
078    };
079
080    static public final String[] ELEMENTS = {
081        JSML,
082        DIV,
083        VOICE,
084        SAYAS,
085        PHONEME,
086        EMPHASIS,
087        BREAK,
088        PROSODY,
089        MARKER,
090        ENGINE
091    };
092
093    static public final String[][] ELEMENT_ATTRIBUTES = {
094        JSML_ATTRIBUTES,
095        DIV_ATTRIBUTES,
096        VOICE_ATTRIBUTES,
097        SAYAS_ATTRIBUTES,
098        PHONEME_ATTRIBUTES,
099        EMPHASIS_ATTRIBUTES,
100        BREAK_ATTRIBUTES,
101        PROSODY_ATTRIBUTES,
102        MARKER_ATTRIBUTES,
103        ENGINE_ATTRIBUTES
104    };
105
106    
107    /*
108     * Commands to be encoded in the text.
109     */
110    static public final String COMMAND_PREFIX = "/";
111    static public final String COMMAND_SUFFIX = "/";
112    static public final String DATA_PREFIX = "[";
113    static public final String DATA_SUFFIX = "]";
114    static public final String ELEMENT_START = "start";
115    static public final String ELEMENT_END = "end";
116    
117    /**
118     * Class constructor.
119     */
120    public TextSynthesizerQueueItem() {
121        super();
122    }
123
124    /**
125     * Gets the type of this queue item.
126     *
127     * @return a <code>String</code> for debug purposes
128     */
129    public String getTypeString() {
130        if (isPlainText()) {
131            return "Plain-text String";
132        } else if (getSource() instanceof String) {
133            return "JSML from String";
134        } else if (getSource() instanceof Speakable) {
135            return "JSML from Speakable";
136        } else if (getSource() instanceof URL) {
137            return "JSML from URL";
138        } else {
139            return "Unknown Output";
140        }
141    }
142
143    /**
144     * Appends the text for this node to the given StringBuffer.
145     *
146     * @param n the node to traverse in depth-first order
147     * @param buf the buffer to append text to
148     */
149    protected void linearize(Node n, StringBuffer buf) {
150        StringBuffer endText = processNode(n, buf);
151        for (Node child = n.getFirstChild();
152             child != null;
153             child = child.getNextSibling()) {
154            linearize(child, buf);
155        }
156
157        if (endText != null) {
158            buf.append(endText);
159        }
160    }
161
162    /**
163     * Adds text for just this node, and returns any text that might
164     * be needed to undo the effects of this node after it is
165     * processed.
166     *
167     * @param n the node to traverse in depth-first order
168     * @param buf the buffer to append text to
169     *
170     * @return a <code>String</code> containing text to undo the
171     *   effects of the node
172     */
173    protected StringBuffer processNode(Node n, StringBuffer buf) {
174        StringBuffer endText = null;
175        
176        int type = n.getNodeType();
177        switch (type) {
178            case Node.ATTRIBUTE_NODE:
179                 break;
180                 
181            case Node.DOCUMENT_NODE:
182                break;
183                
184            case Node.ELEMENT_NODE:
185                endText = processElement((Element) n, buf);
186                break;
187                
188            case Node.TEXT_NODE:
189                buf.append(((Text) n).getData());
190                break;
191
192            // Pass processing instructions (e.g., <?blah?>
193            // right on to the synthesizer.  These types of things
194            // probably should not be used.  Instead the 'engine'
195            // element is probably the best thing to do.
196            //
197            case Node.PROCESSING_INSTRUCTION_NODE:
198                break;
199                
200            // The document type had better be JSML.
201            //
202            case Node.DOCUMENT_TYPE_NODE:
203                break;
204
205            // I think NOTATION nodes are only DTD's.
206            //
207            case Node.NOTATION_NODE:
208                break;
209
210            // Should not get COMMENTS because the JSMLParser
211            // ignores them.
212            //
213            case Node.COMMENT_NODE:
214                break;
215
216            // Should not get CDATA because the JSMLParser is
217            // coalescing.
218            //    
219            case Node.CDATA_SECTION_NODE:
220                break;
221
222            // Should not get ENTITY related notes because
223            // entities are expanded by the JSMLParser
224            //
225            case Node.ENTITY_NODE:
226            case Node.ENTITY_REFERENCE_NODE:
227                break;
228
229            // Should not get DOCUMENT_FRAGMENT nodes because I
230            // [[[WDW]]] think they are only created via the API's
231            // and cannot be defined via content.
232            //
233            case Node.DOCUMENT_FRAGMENT_NODE:
234                break;
235
236            default:
237                break;
238        }
239        
240        return endText;
241    }
242
243    /**
244     * Adds any commands for this element and returns any text that might
245     * be needed to undo the effects of this element after it is processed.
246     *
247     * @param element the element to traverse in depth-first order
248     * @param buf the buffer to append text to
249     *
250     * @return a <code>String</code> containing text to undo the
251     *   effects of the element
252     */
253    protected StringBuffer processElement(Element element, StringBuffer buf) {
254        StringBuffer endText;
255        StringBuffer attributeText = null;
256        
257        String elementName = element.getTagName();
258        for (int i = 0; i < ELEMENTS.length; i++) {
259            if (ELEMENTS[i].equals(elementName)) {
260                attributeText = processAttributes(
261                    element, ELEMENT_ATTRIBUTES[i]);
262                break;
263            }
264        }
265
266        buf.append(COMMAND_PREFIX + elementName + " " + ELEMENT_START);
267        if (attributeText != null) {
268            buf.append(attributeText);
269        }
270        buf.append(COMMAND_SUFFIX);
271
272        endText = new StringBuffer(
273            COMMAND_PREFIX + elementName + " " + ELEMENT_END);
274        if (attributeText != null) {
275            endText.append(attributeText);
276        }
277        endText.append(COMMAND_SUFFIX);
278
279        return endText;
280    }
281
282    /**
283     * Gets the list of attributes of the element and returns them in
284     * a <code>StringBuffer</code>.
285     *
286     * @param element the element containing attributes (if any)
287     * @param attributes the allowed attributes for
288     *   <code>element</code>
289     *
290     * @return a buffer containing the attributes in text form
291     */
292    protected StringBuffer processAttributes(Element element,
293                                             String[] attributes) {
294        StringBuffer attributeText = new StringBuffer();
295        for (int i = 0; i < attributes.length; i++) {
296            if (element.hasAttribute(attributes[i])) {
297                String data = element.getAttribute(attributes[i]);
298                attributeText.append(
299                    DATA_PREFIX + attributes[i] + "=" + data + DATA_SUFFIX);
300            }
301        }
302        return attributeText;
303    }
304       
305    /**
306     * Gets the text form of this queue item.
307     *
308     * @return the text form of this queue item.
309     */
310    public String getEngineText() {
311        if (isPlainText()) {
312            return text;
313        } else {
314            StringBuffer textBuffer = new StringBuffer();
315            Document document = getDocument();
316            linearize(document, textBuffer);
317            return(textBuffer.toString());
318        }
319    }
320}