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}