/// <summary>
        /// Get word text for number domain.
        /// </summary>
        /// <param name="word">Given ScriptWord.</param>
        /// <param name="sentenceId">Sentence Id.</param>
        /// <param name="wordIndex">Word index.</param>
        /// <param name="digitals">Digital words.</param>
        /// <returns>Word text.</returns>
        private static string GetNumberDomainWordText(ScriptWord word, string sentenceId,
            int wordIndex, Dictionary<string, DigitalWordItem> digitals)
        {
            string text = word.Grapheme.ToUpperInvariant();

            string key = DigitalWordItem.GetKey(sentenceId, wordIndex);
            if (digitals.ContainsKey(key))
            {
                text += "@" + digitals[key].Group.ToString(System.Globalization.CultureInfo.InvariantCulture);
            }
            else
            {
                if (word.Break > TtsBreak.Word)
                {
                    text += "#3";
                }
                else
                {
                    text += "#1";
                }
            }

            return text;
        }
        /// <summary>
        /// Calculate PosInSentence feature for a given word
        /// Change it to public for code re-use in script sentence.
        /// </summary>
        /// <param name="preWord">Previous word of target word to calculate.</param>
        /// <param name="word">Target word to calculate.</param>
        /// <returns>PosInSentence feature.</returns>
        public static PosInSentence CalculatePosInSentence(ScriptWord preWord, ScriptWord word)
        {
            int row = (int)((preWord == null) ? TtsBreak.Sentence : preWord.Break) - (int)TtsBreak.Word;
            int column = (int)word.Break - (int)TtsBreak.Word;

            // #0, a word is connected with the word following it. Like the liason in French
            // Here treat it as word boundary
            column = column < 0 ? 0 : column;
            row = row < 0 ? 0 : row;

            PosInSentence pis = _posInSentenceTrans[row][column];

            return pis;
        }
        /// <summary>
        /// Get script words from utterance.
        /// </summary>
        /// <param name="utt">Utterance.</param>
        /// <param name="isOovWord">Return whether each word is oov.</param>
        /// <returns>Script words.</returns>
        public Collection<ScriptWord> GetScriptWords(SP.TtsUtterance utt, Collection<bool> isOovWord)
        {
            if (utt == null)
            {
                throw new ArgumentNullException("utt");
            }

            Collection<ScriptWord> words = new Collection<ScriptWord>();
            for (int i = 0; i < utt.Words.Count; ++i)
            {
                SP.TtsWord word = utt.Words[i];
                if (IsCommonWord(word))
                {
                    ScriptWord scriptWord = new ScriptWord();

                    // Currently runtime will append a punctuation word for the sentence that has no 
                    // punctuation at the end.
                    if (string.IsNullOrEmpty(word.WordText) && (i == utt.Words.Count - 1) &&
                        word.WordType == SP.TtsWordType.WT_PUNCTUATION)
                    {
                        // Don't add the word to keep consistence with original input text.
                        continue;
                    }

                    scriptWord.Grapheme = word.WordText;
                    if (!string.IsNullOrEmpty(word.Pronunciation))
                    {
                        scriptWord.Pronunciation = word.Pronunciation.ToLowerInvariant();
                    }

                    ushort posTaggerPos = _engine.PosTable.GetPOSTaggerPOS(checked((ushort)word.Pos));
                    scriptWord.PosString = _engine.PosTable.IdToString(posTaggerPos);
                    scriptWord.DetailedPosString = _engine.PosTable.IdToString(checked((ushort)word.Pos));
                    scriptWord.NETypeText = word.NETypeText;
                    scriptWord.OffsetInString = (int)word.TextOffset;
                    scriptWord.LengthInString = (int)word.TextLength;
                    scriptWord.PronSource = (TtsPronSource)word.PronSource;
                    scriptWord.RegularText = word.WordRegularText;

                    switch (word.WordType)
                    {
                        case SP.TtsWordType.WT_NORMAL:
                        case SP.TtsWordType.WT_SPELLOUT:
                            scriptWord.WordType = WordType.Normal;
                            break;
                        case SP.TtsWordType.WT_PUNCTUATION:
                            scriptWord.WordType = WordType.Punctuation;
                            break;
                    }

                    switch (word.Emphasis)
                    {
                        case SP.TtsEmphasis.EMPH_YES:
                            scriptWord.Emphasis = TtsEmphasis.Yes;
                            break;
                        case SP.TtsEmphasis.EMPH_NONE:
                            scriptWord.Emphasis = TtsEmphasis.None;
                            break;
                    }

                    switch (word.BreakLevel)
                    {
                        case SP.TtsBreakLevel.BK_IDX_SYLLABLE:
                            scriptWord.Break = TtsBreak.Syllable;
                            break;
                        case SP.TtsBreakLevel.BK_IDX_INTERM_PHRASE:
                            scriptWord.Break = TtsBreak.InterPhrase;
                            break;
                        case SP.TtsBreakLevel.BK_IDX_INTONA_PHRASE:
                            scriptWord.Break = TtsBreak.IntonationPhrase;
                            break;
                        case SP.TtsBreakLevel.BK_IDX_SENTENCE:
                            scriptWord.Break = TtsBreak.Sentence;
                            break;
                    }

                    if (word.ToBIFinalBoundaryTone != (uint)TtsTobiBoundary.NoBoundaryTone)
                    {
                        Offline.TtsTobiBoundaryToneSet boundarySet = new Offline.TtsTobiBoundaryToneSet();
                        scriptWord.TobiFinalBoundaryTone = TobiLabel.Create(boundarySet.IdItems[(uint)word.ToBIFinalBoundaryTone]);
                    }

                    words.Add(scriptWord);
                    isOovWord.Add(IsOov(word));
                }
            }

            return words;
        }
        /// <summary>
        /// Dump the data in the words.
        /// </summary>
        /// <param name="sentence">The script sentence which to store the data dumped from the words.</param>
        /// <param name="utt">The utterance.</param>
        /// <param name="ttsEngine">The object ttsEngine to help to convert the Pos and get sentence id.</param>
        /// <param name="scriptLanguage">The language of the script.</param>
        private static void DumpWords(ScriptSentence sentence, SP.TtsUtterance utt,
            SP.TtsEngine ttsEngine, Language scriptLanguage)
        {
            Debug.Assert(sentence != null, "Sentence should not be null");
            Debug.Assert(utt != null, "Utt should not be null");
            Debug.Assert(ttsEngine != null, "ttsEngine should not be null");

            // Phone index to mark the phone in the Utt.Phones
            int phoneIndex = 0;

            // F0 index to mark the start position in the Utt.Sccoustic.F0s
            int f0StartIndex = 0;

            // Unit index to mark the unit in the Utt.Units
            int unitIndex = 0;

            // Word index to mark the position in the Utt.Words
            int wordIndex = 0;

            foreach (SP.TtsWord word in utt.Words)
            {
                if (word.WordText != null)
                {
                    ScriptWord scriptWord = new ScriptWord();

                    // Tag the language to the word level if there is not single language in the utt.
                    // The major language (the most word count with this language) will be tag on the 
                    // script level, others tag on the word level. 
                    if ((Language)word.LangId != scriptLanguage)
                    {
                        scriptWord.Language = (Language)word.LangId;
                    }

                    // According to the schema, if the word is "silence", there should be not
                    // value in the scriptWord pronunciation. Means: <w v=""
                    if (word.WordType != TtsWordType.WT_SILENCE)
                    {
                        scriptWord.Grapheme = word.WordText;
                    }

                    if (!string.IsNullOrEmpty(word.Pronunciation))
                    {
                        scriptWord.Pronunciation = word.Pronunciation.ToLowerInvariant();
                    }

                    scriptWord.WordType = ConvertWordType(word);

                    // Dump the Part-Of-Speech.
                    // If the word is "sil", the word text is " ", the pos id is 65535, out of boundary.
                    // In this case, will not dump the pos.
                    if (!string.IsNullOrEmpty(word.WordText.Trim()))
                    {
                        scriptWord.PosString = ttsEngine.PosTable.IdToString(word.Pos);
                    }

                    scriptWord.Break = (TtsBreak)word.BreakLevel;
                    scriptWord.Emphasis = (TtsEmphasis)word.Emphasis;
                    scriptWord.TobiFinalBoundaryTone = ConvertTobiFBT(word.ToBIFinalBoundaryTone);
                    scriptWord.PronSource = (TtsPronSource)word.PronSource;
                    scriptWord.OffsetInString = (int)word.TextOffset;
                    scriptWord.LengthInString = (int)word.TextLength;
                    DumpSyllables(scriptWord, utt, word, ref phoneIndex, ref unitIndex, ref f0StartIndex, ttsEngine);
                    sentence.Words.Add(scriptWord);
                }
                else
                {
                    string message = Helper.NeutralFormat("The word text of word [{0}]: \"{1}\" in the" +
                        "utterance is empty.", wordIndex, word.WordText);
                    throw new InvalidDataException(message);
                }

                wordIndex++;
            }
        }
        /// <summary>
        /// Syllabify one tts word entry.
        /// </summary>
        /// <param name="word">Word to syllabify.</param>
        /// <returns>Sliced word pronunciation.</returns>
        public string SliceWord(ScriptWord word)
        {
            if (word == null)
            {
                throw new ArgumentNullException("word");
            }

            if (string.IsNullOrEmpty(word.Grapheme))
            {
                string message = string.Format(CultureInfo.InvariantCulture,
                    "word.Grapheme should not be null or empty.");
                throw new ArgumentNullException("word", message);
            }

            string pron = word.GetPronunciation(_phoneSet);
            string slicePron = string.Empty;
            if (!string.IsNullOrEmpty(pron))
            {
                slicePron = RewritePhones2Units(_sliceData, pron);
                if (!string.IsNullOrEmpty(slicePron))
                {
                    slicePron = Regex.Replace(slicePron, @"\.\s+\.", ".");
                }
            }

            return slicePron;
        }
        /// <summary>
        /// Appends a punctuation word in the end of given utterance.
        /// </summary>
        /// <param name="utterance">
        /// The given utterance.
        /// </param>
        /// <param name="scriptWord">
        /// The script word.
        /// </param>
        /// <returns>
        /// The phoneme count of the given word.
        /// </returns>
        /// <exception cref="InvalidDataException">
        /// Exception.
        /// </exception>
        private int AppendPunctuationWord(TtsUtterance utterance, ScriptWord scriptWord)
        {
            TtsWord word = utterance.AppendNewWord();
            word.LangId = (ushort)scriptWord.Language;
            word.BreakLevel = (TtsBreakLevel)scriptWord.Break;
            word.Emphasis = (TtsEmphasis)scriptWord.Emphasis;
            word.WordText = scriptWord.Grapheme;
            word.NETypeText = scriptWord.NETypeText;
            word.WordType = TtsWordType.WT_PUNCTUATION;

            // There is no phoneme for punctuation word.
            return 0;
        }
        /// <summary>
        /// Read and parse word data from the XML text reader to utterance.
        /// </summary>
        /// <param name="reader">XML text reader to read data from.</param>
        /// <param name="utterance">Target utterance to save result words.</param>
        private static void ProcessWord(XmlTextReader reader, TtsUtterance utterance)
        {
            ScriptWord word = new ScriptWord(utterance.Script.Language);
            word.Grapheme = reader.GetAttribute("val");

            if (reader.GetAttribute("p") != null)
            {
                word.Pronunciation = reader.GetAttribute("p");
            }

            if (reader.GetAttribute("pos") != null)
            {
                word.Pos = (PartOfSpeech)Enum.Parse(typeof(PartOfSpeech), reader.GetAttribute("pos"));
            }

            if (reader.GetAttribute("emphasis") != null)
            {
                word.Emphasis =
                    (TtsEmphasis)Enum.Parse(typeof(TtsEmphasis), reader.GetAttribute("emphasis"));
            }

            if (reader.GetAttribute("break") != null)
            {
                word.Break = (TtsBreak)Enum.Parse(typeof(TtsBreak), reader.GetAttribute("break"));
            }

            if (reader.GetAttribute("type") != null)
            {
                word.WordType = (WordType)Enum.Parse(typeof(WordType), reader.GetAttribute("type"));
            }

            utterance.Script.Words.Add(word);
            reader.Skip();
        }
        /// <summary>
        /// Build syllable collection for a given word.
        /// </summary>
        /// <param name="word">Word to process.</param>
        private static void BuildSyllables(ScriptWord word)
        {
            if (word == null)
            {
                throw new ArgumentNullException("word");
            }

            if (word.Pronunciation == null)
            {
                throw new ArgumentException("word.Pronunciation is null");
            }

            word.Syllables.Clear();

            string[] syllableTexts = Core.Pronunciation.SplitIntoSyllables(word.Pronunciation);
            for (int syllableIndex = 0; syllableIndex < syllableTexts.Length; syllableIndex++)
            {
                ScriptSyllable syllable = new ScriptSyllable();

                syllable.Text = syllableTexts[syllableIndex];

                syllable.TtsBreak = (syllableIndex == syllableTexts.Length - 1) ?
                    word.Break : TtsBreak.Syllable;
                syllable.Stress = Core.Pronunciation.GetStress(syllable.Text);
                syllable.TtsEmphasis = (syllable.Stress != TtsStress.None) ?
                    word.Emphasis : TtsEmphasis.None;

                word.Syllables.Add(syllable);
            }
        }
        /// <summary>
        /// Load the attributes for a given word.
        /// </summary>
        /// <param name="word">ScriptWord.</param>
        /// <param name="reader">XmlTextReader.</param>
        /// <param name="scriptLanguage">The language of the script.</param>
        private static void LoadWordAttributes(ScriptWord word, XmlTextReader reader,
            Language scriptLanguage)
        {
            Debug.Assert(word != null);
            Debug.Assert(reader != null);

            string wordLanguage = reader.GetAttribute("language");
            if (!string.IsNullOrEmpty(wordLanguage) &&
                Localor.StringToLanguage(wordLanguage) != scriptLanguage)
            {
                word.Language = Localor.StringToLanguage(wordLanguage);
            }

            word.Grapheme = reader.GetAttribute("v");

            string pron = reader.GetAttribute("p");
            if (!string.IsNullOrEmpty(pron))
            {
                word.Pronunciation = pron;
            }

            word.AcceptGrapheme = reader.GetAttribute("av");
            string acceptPron = reader.GetAttribute("ap");
            if (!string.IsNullOrEmpty(acceptPron))
            {
                word.AcceptPronunciation = acceptPron;
            }

            string type = reader.GetAttribute("type");
            if (!string.IsNullOrEmpty(type))
            {
                word.WordType = ScriptWord.StringToWordType(type);
            }

            if (word.Grapheme == null || word.Grapheme.Length == 0)
            {
                if (word.WordType != WordType.Silence &&
                    word.WordType != WordType.Punctuation)
                {
                    throw new InvalidDataException(Helper.NeutralFormat(
                        "Line [{0}]: only silence word, prosody boundary or punctuation can have null/empty word grapheme",
                        reader.LineNumber));
                }
            }
            else
            {
                if (word.WordType == WordType.Silence)
                {
                    throw new InvalidDataException(Helper.NeutralFormat(
                        "Line [{0}]: silence word or prosody boundary should have empty word grapheme",
                        reader.LineNumber));
                }
            }

            string pos = reader.GetAttribute("pos");
            if (!string.IsNullOrEmpty(pos))
            {
                word.PosString = pos;
            }

            string expansion = reader.GetAttribute("exp");
            if (!string.IsNullOrEmpty(expansion))
            {
                word.Expansion = expansion;
            }

            string emphasis = reader.GetAttribute("em");
            if (!string.IsNullOrEmpty(emphasis))
            {
                word.Emphasis = ScriptWord.StringToEmphasis(emphasis);
            }

            string breakLevel = reader.GetAttribute("br");
            if (!string.IsNullOrEmpty(breakLevel))
            {
                word.Break = ScriptWord.StringToBreak(breakLevel);
            }

            string breakAskLevel = reader.GetAttribute("bra");
            if (!string.IsNullOrEmpty(breakAskLevel))
            {
                word.BreakAsk = ScriptWord.StringToBreak(breakAskLevel);
            }
            else
            {
                word.BreakAsk = ScriptWord.UndefinedBreakAsk;
            }

            string breakProb = reader.GetAttribute("brp");
            if (!string.IsNullOrEmpty(breakProb))
            {
                word.BreakProb = float.Parse(breakProb, CultureInfo.InvariantCulture);
            }
            else
            {
                word.BreakProb = ScriptWord.DefaultProbability;
            }

            string wordTone = reader.GetAttribute("wt");
            if (!string.IsNullOrEmpty(wordTone))
            {
                word.WordTone = ScriptWord.StringToWordTone(wordTone);
            }

            string tobiibt = reader.GetAttribute("tobiibt");
            if (!string.IsNullOrEmpty(tobiibt))
            {
                word.TobiInitialBoundaryTone = new TobiLabel(tobiibt);
            }

            string tobifbt = reader.GetAttribute("tobifbt");
            if (!string.IsNullOrEmpty(tobifbt))
            {
                word.TobiFinalBoundaryTone = new TobiLabel(tobifbt);
            }

            string domain = reader.GetAttribute("domain");
            if (!string.IsNullOrEmpty(domain))
            {
                word.AcousticDomainTag = domain;
            }

            string nusTag = reader.GetAttribute("nus");
            if (!string.IsNullOrEmpty(nusTag))
            {
                word.NusTag = nusTag;
            }

            string regularText = reader.GetAttribute("regularText");
            if (!string.IsNullOrEmpty(regularText))
            {
                word.RegularText = regularText;
            }

            string sp = reader.GetAttribute("sp");
            if (!string.IsNullOrEmpty(sp))
            {
                word.ShallowParseTag = sp;
            }

            string tcgppScore = reader.GetAttribute("tcgppScore");
            if (!string.IsNullOrEmpty(tcgppScore))
            {
                word.TcgppScores = tcgppScore;
            }

            string pronSource = reader.GetAttribute("pronSource");
            if (!string.IsNullOrEmpty(pronSource))
            {
                word.PronSource = ScriptWord.StringToPronSource(pronSource);
            }

            string netype = reader.GetAttribute("netype");
            if (!string.IsNullOrEmpty(netype))
            {
                word.NETypeText = netype;
            }

            if (word.WordType != WordType.Silence)
            {
                string offset = reader.GetAttribute("offset");
                if (!string.IsNullOrEmpty(offset))
                {
                    word.OffsetInString = int.Parse(offset);
                }

                string length = reader.GetAttribute("length");
                if (!string.IsNullOrEmpty(length))
                {
                    word.LengthInString = int.Parse(length);
                }
            }
        }
        /// <summary>
        /// Load one script word from the xmltextreader.
        /// </summary>
        /// <param name="reader">XmlTextReader.</param>
        /// <param name="contentController">ContentControler.</param>
        /// <param name="language">The language of the script.</param>
        /// <returns>ScriptWord that read.</returns>
        public static ScriptWord LoadWord(XmlTextReader reader, object contentController, Language language)
        {
            Debug.Assert(reader != null);

            ContentControler scriptContentController = new ContentControler();
            if (contentController is ContentControler)
            {
                scriptContentController = contentController as ContentControler;
            }
            else if (contentController != null)
            {
                throw new ArgumentException("Invalid contentController type");
            }

            ScriptWord word = new ScriptWord(language);

            // load attributes
            LoadWordAttributes(word, reader, language);

            // load syllables
            // remember that word can have no syllable list
            if (!reader.IsEmptyElement)
            {
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element && reader.Name == "syls")
                    {
                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element && reader.Name == "syl")
                            {
                                ScriptSyllable syllable = LoadSyllable(reader, language);
                                syllable.Word = word;
                                word.Syllables.Add(syllable);
                            }
                            else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "syls")
                            {
                                break;
                            }
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.Element && reader.Name == "acoustics")
                    {
                        word.Acoustics = new ScriptAcoustics();
                        word.Acoustics.ParseFromXml(reader);
                    }
                    else if (reader.NodeType == XmlNodeType.Element && reader.Name == "comments")
                    {
                        if (scriptContentController.LoadComments)
                        {
                            word.TtsXmlComments.Parse(reader);
                            word.TtsXmlComments.Tag = word;
                        }
                        else
                        {
                            reader.Skip();
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "w")
                    {
                        break;
                    }
                }
            }

            return word;
        }
        /// <summary>
        /// Sync word text to item text.
        /// </summary>
        /// <param name="scriptWord">Script word to be synced.</param>
        public static void SyncWordChangesToItem(ScriptWord scriptWord)
        {
            if (scriptWord == null)
            {
                throw new ArgumentNullException("scriptWord");
            }

            if (scriptWord.Sentence == null)
            {
                throw new ArgumentException("scriptWord.Sentence is null");
            }

            if (scriptWord.Sentence.ScriptItem == null)
            {
                throw new ArgumentException("scriptWord.Sentence.ScriptItem is null");
            }

            SyncItemTextFromWordList(scriptWord.Sentence.ScriptItem);
        }
        /// <summary>
        /// Scritp word to be displayed.
        /// </summary>
        /// <param name="word">Script word.</param>
        /// <returns>String.</returns>
        public static string BuildDisplayedWordText(ScriptWord word)
        {
            if (word == null)
            {
                throw new ArgumentNullException("word");
            }

            if (string.IsNullOrEmpty(word.Grapheme))
            {
                throw new ArgumentException("word.Grapheme");
            }

            StringBuilder displayedWordText = new StringBuilder();
            displayedWordText.AppendFormat("{0}", word.Grapheme);
            if (word.Emphasis == TtsEmphasis.Yes)
            {
                displayedWordText.Append(" *");
            }

            string breakString = ScriptWord.BreakToString(word.Break);
            if (!string.IsNullOrEmpty(breakString))
            {
                if (word.Break == TtsBreak.Syllable)
                {
                    displayedWordText.Append(" _");
                }
                else
                {
                    displayedWordText.Append(" #");
                }

                displayedWordText.Append(breakString);
            }

            return displayedWordText.ToString();
        }
        /// <summary>
        /// Get word text for acronym domain.
        /// </summary>
        /// <param name="word">Given ScriptWord.</param>
        /// <param name="sentenceId">Sentence Id.</param>
        /// <param name="wordIndex">Word index.</param>
        /// <param name="acronyms">Acronym words.</param>
        /// <returns>Word text.</returns>
        private static string GetAcronymDomainWordText(ScriptWord word, string sentenceId,
            int wordIndex, Dictionary<string, AcronymWordItem> acronyms)
        {
            string key = AcronymWordItem.GetKey(sentenceId, wordIndex);

            string text = acronyms[key].Word.ToUpperInvariant();
            
            text += "@" + acronyms[key].Group.ToString(System.Globalization.CultureInfo.InvariantCulture);

            return text;
        }
        /// <summary>
        /// Find previos word, which is normal word
        /// Change it to public for code re-use in script sentence.
        /// </summary>
        /// <param name="words">Word collection to search.</param>
        /// <param name="word">Word to find previous word for.</param>
        /// <returns>Found word.</returns>
        public static ScriptWord FindPreviousWord(Collection<ScriptWord> words, ScriptWord word)
        {
            if (words == null)
            {
                throw new ArgumentNullException("words");
            }

            if (word == null)
            {
                throw new ArgumentNullException("word");
            }

            int index = words.IndexOf(word);
            if (index == -1)
            {
                throw new ArgumentOutOfRangeException("word");
            }

            while (index - 1 >= 0)
            {
                if (words[index - 1].WordType == WordType.Normal)
                {
                    return words[index - 1];
                }

                --index;
            }

            return null;
        }
        /// <summary>
        /// Converts one word instance to script named entity.
        /// </summary>
        /// <param name="word">The word instance to convert.</param>
        /// <returns>The converted script named entity instance.</returns>
        private ScriptNamedEntity ToScriptNamedEntity(ScriptWord word)
        {
            Helper.ThrowIfNull(word);
            if (string.IsNullOrEmpty(word.NamedEntityTypeString))
            {
                throw new InvalidDataException(Helper.NeutralFormat(
                    "The type of the named entity [{0}] should not be empty.", word.NamedEntityTypeString));
            }

            ScriptNamedEntity entity = new ScriptNamedEntity();

            entity.Text = word.Grapheme;
            entity.PosString = word.PosString;
            entity.Type = word.NamedEntityTypeString;

            entity.Start = word.SubWords[0];
            entity.End = word.SubWords[word.SubWords.Count - 1];

            return entity;
        }
 /// <summary>
 /// Fix the ending punctuation of the sentence, this is, to make sure 
 /// The sentence end with Period punctuation.
 /// </summary>
 /// <param name="words">Word collection of the sentence.</param>
 /// <param name="language">Language of the sentence.</param>
 private static void FixEndingPunctuation(Collection<ScriptWord> words, Language language)
 {
     if (words.Count > 0)
     {
         ScriptWord lastWord = words[words.Count - 1];
         if (lastWord.WordType == WordType.Normal)
         {
             ScriptWord word = new ScriptWord(language);
             word.Grapheme = ".";
             word.WordType = WordType.Period;
             word.Break = Localor.MapWordType2Break(word.WordType);
             words.Add(word);
         }
         else if (lastWord.WordType == WordType.OtherPunctuation)
         {
             // upgrade this word break level
             lastWord.WordType = WordType.Period;
             lastWord.Break = Localor.MapWordType2Break(lastWord.WordType);
         }
     }
 }
        /// <summary>
        /// Converts one named entity to script word instance.
        /// </summary>
        /// <param name="entity">The named entity instance to convert.</param>
        /// <returns>The converted word instance.</returns>
        private ScriptWord ToScriptWord(ScriptNamedEntity entity)
        {
            Helper.ThrowIfNull(entity);
            if (string.IsNullOrEmpty(entity.Type))
            {
                throw new InvalidDataException(Helper.NeutralFormat(
                    "The type of the named entity [{0}] should not be empty.", entity.Text));
            }

            ScriptWord word = new ScriptWord(entity.Start.Language);

            word.Break = entity.End.Break;

            word.Grapheme = entity.Text;
            word.PosString = entity.PosString;

            if (word.PosString == ScriptNamedEntity.DefaultEmptyPosString)
            {
                word.PosString = ScriptNamedEntity.DefaultEntityPosString;
            }

            word.NamedEntityTypeString = entity.Type;
            word.Sentence = entity.Start.Sentence;

            word.SubWords = new Collection<ScriptWord>();
            StringBuilder pronunciation = new StringBuilder();
            for (int i = Words.IndexOf(entity.Start); i <= Words.IndexOf(entity.End); i++)
            {
                word.SubWords.Add(Words[i]);
                if (!string.IsNullOrEmpty(Words[i].Pronunciation))
                {
                    if (pronunciation.Length != 0)
                    {
                        pronunciation.AppendFormat(" {0} ", Pronunciation.WordPronBoundaryString);
                    }

                    pronunciation.Append(Words[i].Pronunciation);
                }
            }

            word.Pronunciation = pronunciation.ToString();

            return word;
        }
        /// <summary>
        /// Previos word building, process one token of word.
        /// </summary>
        /// <param name="words">Result word list.</param>
        /// <param name="language">Language of the sentence.</param>
        /// <param name="tokenMatch">Token matched.</param>
        /// <param name="wordIndex">Word index of current word.</param>
        /// <returns>Word index of next word.</returns>
        private int PreBuildWord(Collection<ScriptWord> words, Language language,
            Match tokenMatch, int wordIndex)
        {
            bool tagged = true;

            // 0 is the whole "((\S+)/(\S+))|(\S+)"
            // 1 is "((\S+)/(\S+))|"
            // 2 is "(\S+)/"
            // 3 is "/(\S+)"
            // 4 is "|(\S+)"
            string content = tokenMatch.Groups[1].Value;

            if (string.IsNullOrEmpty(content))
            {
                tagged = false;
                content = tokenMatch.Groups[4].Value;
            }
            else
            {
                content = tokenMatch.Groups[2].Value;
            }

            Match breakMatch = Regex.Match(content, @"^#([0|1|2|3|4])([FfcRr]?)$");
            Match wordToneMatch = Regex.Match(content, @"^#([FfcRr])$");
            Match emphasisMatch = Regex.Match(content, @"^\*[234]?$");
            Match puncMatch = Regex.Match(content, @"^" + PunctuationPattern + "$");

            if (emphasisMatch.Success)
            {
                if (wordIndex >= 0)
                {
                    words[wordIndex].Emphasis = TtsEmphasis.Yes;
                    words[wordIndex].EmphasisTag = content;
                }
            }
            else if (wordToneMatch.Success)
            {
                if (wordIndex >= 0)
                {
                    words[wordIndex].WordTone =
                        ScriptItem.LabelToWordTone(wordToneMatch.Groups[1].Value);
                    words[wordIndex].WordToneTag = content;
                }
            }
            else if (breakMatch.Success)
            {
                if (wordIndex >= 0)
                {
                    // Upgrade one level for #1 for word break level.
                    words[wordIndex].Break =
                        (TtsBreak)(int.Parse(breakMatch.Groups[1].Value,
                                   CultureInfo.InvariantCulture) + 1);
                    words[wordIndex].BreakTag = content;

                    if (!string.IsNullOrEmpty(breakMatch.Groups[2].Value))
                    {
                        words[wordIndex].WordTone =
                            ScriptItem.LabelToWordTone(breakMatch.Groups[2].Value);
                        words[wordIndex].WordToneTag = content;
                    }
                }
            }
            else if (puncMatch.Success)
            {
                ScriptWord word = new ScriptWord(language);
                word.Grapheme = content;
                word.WordType = Localor.MapPunctuation(puncMatch.Groups[1].Value,
                    PunctuationPattern);
                word.Break = Localor.MapWordType2Break(word.WordType);
                if (tagged)
                {
                    word.PosTag = tokenMatch.Groups[3].Value;
                }

                words.Add(word);
            }
            else
            {
                ScriptWord word = new ScriptWord(language);
                word.Grapheme = content;
                if (tagged)
                {
                    word.PosTag = tokenMatch.Groups[3].Value;
                }

                word.AccessingUnits += delegate
                {
                    RefreshUnits();
                };

                words.Add(word);
                wordIndex = words.Count - 1;
            }

            return wordIndex;
        }
 /// <summary>
 /// Get intonation phrase of the word.
 /// </summary>
 /// <param name="word">Script word.</param>
 /// <returns>Intonation phrase.</returns>
 public ScriptIntonationPhrase GetIntonationPhrase(ScriptWord word)
 {
     ScriptIntermediatePhrase intermediatePhrase = GetIntermediatePhrase(word);
     return intermediatePhrase == null ? null : intermediatePhrase.IntonationPhrase;
 }
        /// <summary>
        /// Insert silence word to script.
        /// </summary>
        /// <param name="scriptSentence">Script sentence.</param>
        /// <param name="wordIndex">To be insert word's position.</param>
        /// <param name="phoneme">The phoneme string.</param>
        public static void InsertSilenceWord(ScriptSentence scriptSentence, int wordIndex, string phoneme)
        {
            Debug.Assert(Phoneme.IsSilenceFeature(phoneme), "The phoneme should have silence feature");

            ScriptWord silenceWord = new ScriptWord();
            silenceWord.WordType = WordType.Silence;
            silenceWord.Pronunciation = Phoneme.ToRuntime(phoneme);
            silenceWord.Sentence = scriptSentence;
            ScriptSyllable silenceSyllable = new ScriptSyllable();
            silenceSyllable.Word = silenceWord;
            silenceWord.Syllables.Add(silenceSyllable);
            ScriptPhone silencePhone = new ScriptPhone(phoneme);
            silencePhone.Syllable = silenceSyllable;
            silenceWord.Syllables[0].Phones.Add(silencePhone);

            scriptSentence.Words.Insert(wordIndex, silenceWord);
        }
        /// <summary>
        /// Get intermediate phrase.
        /// </summary>
        /// <param name="word">Script word.</param>
        /// <returns>Intermediate phrase.</returns>
        public ScriptIntermediatePhrase GetIntermediatePhrase(ScriptWord word)
        {
            ScriptIntermediatePhrase intermediatePhrase = null;
            foreach (ScriptIntonationPhrase phrase in IntonationPhrases)
            {
                intermediatePhrase = phrase.GetIntermediatePhrase(word);
                if (intermediatePhrase != null)
                {
                    break;
                }
            }

            return intermediatePhrase;
        }
        /// <summary>
        /// Appends a normal word in the end of given utterance.
        /// </summary>
        /// <param name="utterance">
        /// The given utterance.
        /// </param>
        /// <param name="scriptWord">
        /// The script word.
        /// </param>
        /// <returns>
        /// The phoneme count of the given word.
        /// </returns>
        /// <exception cref="InvalidDataException">
        /// Exception.
        /// </exception>
        private int AppendNormalWord(TtsUtterance utterance, ScriptWord scriptWord)
        {
            TtsWord word = utterance.AppendNewWord();
            word.LangId = (ushort)scriptWord.Language;
            word.BreakLevel = (TtsBreakLevel)scriptWord.Break;
            word.Emphasis = (TtsEmphasis)scriptWord.Emphasis;
            word.WordText = scriptWord.Grapheme;
            word.NETypeText = scriptWord.NETypeText;
            word.WordRegularText = scriptWord.RegularText;
            word.WordType = TtsWordType.WT_NORMAL;
            word.AcousticDomain = DomainExtension.MapToEnum(scriptWord.AcousticDomainTag);
            word.WordExpansion = scriptWord.Expansion;
            word.ReadablePronunciation = scriptWord.Pronunciation;
            if (!string.IsNullOrEmpty(scriptWord.Pronunciation))
            {
                word.PhoneIds = Phoneme.PronunciationToPhoneIds(Pronunciation.RemoveUnitBoundary(scriptWord.Pronunciation));
            }

            if (NeedPos)
            {
                // Checks pos.
                if (string.IsNullOrEmpty(scriptWord.PosString))
                {
                    throw new InvalidDataException(
                        Helper.NeutralFormat("No POS found in sentence \"{0}\" for word \"{1}\"",
                            scriptWord.Sentence.ScriptItem.Id, scriptWord.Grapheme));
                }

                // Sets pos value.
                word.Pos = (ushort)PosSet.Items[scriptWord.PosString];
                string taggingPos = PosSet.CategoryTaggingPOS[scriptWord.PosString];
                word.POSTaggerPos = (ushort)PosSet.Items[taggingPos];
            }

            // Gets the normal phoneme count.
            ErrorSet errorSet = new ErrorSet();
            int count = scriptWord.GetNormalPhoneNames(PhoneSet, errorSet).Count;
            if (errorSet.Count > 0)
            {
                throw new InvalidDataException(
                    Helper.NeutralFormat("Invalid phone found in sentence \"{0}\" for word \"{1}\"",
                        scriptWord.Sentence.ScriptItem.Id, scriptWord.Grapheme));
            }

            word.TextOffset = (uint)scriptWord.OffsetInString;
            word.TextLength = (uint)scriptWord.LengthInString;

            return count;
        }
        /// <summary>
        /// Delete word from the sentence.
        /// For example: A, B, C, D, E.
        /// Current status: C, and D have been deleted.then word after word dict store: C->D, D->E.
        /// After deleting B, then deleted word after word will store: B->C, C->D, D->E.
        /// </summary>
        /// <param name="scriptWord">Word to be deleted.</param>
        /// <returns>Deleted word position.</returns>
        public int DeleteWord(ScriptWord scriptWord)
        {
            if (scriptWord == null)
            {
                throw new ArgumentNullException("scriptWord");
            }

            int position = _words.IndexOf(scriptWord);

            if (position >= 0)
            {
                _words.Remove(scriptWord);
                _needBuildUnits = true;
            }

            return position;
        }
        /// <summary>
        /// Generate script item from raw text(only generate to word level).
        /// </summary>
        /// <param name="text">Plain text.</param>
        /// <returns>ScriptItem.</returns>
        public ScriptItem GenerateScriptItem(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                throw new ArgumentNullException("text");
            }

            // this function should contain "ProcessMode.TextProcess"
            if ((_mode & ProcessMode.TextProcess) == 0)
            {
                throw new InvalidOperationException("Process mode can only be ProcessMode.TextProcess");
            }

            ScriptItem item = new ScriptItem();
            item.Text = text;

            foreach (SP.TtsUtterance utt in EspUtterances(text))
            {
                using (utt)
                {
                    if (utt.Words.Count == 0)
                    {
                        continue;
                    }

                    ScriptSentence sentence = new ScriptSentence();
                    foreach (SP.TtsWord word in utt.Words)
                    {
                        if (!string.IsNullOrEmpty(word.WordText))
                        {
                            ScriptWord scriptWord = new ScriptWord();
                            scriptWord.Grapheme = word.WordText;

                            if (!string.IsNullOrEmpty(word.Pronunciation))
                            {
                                scriptWord.Pronunciation = word.Pronunciation.ToLowerInvariant();
                            }

                            scriptWord.WordType = WordType.Normal;
                            if (word.WordType == SP.TtsWordType.WT_PUNCTUATION)
                            {
                                scriptWord.WordType = WordType.Punctuation;
                            }

                            scriptWord.PronSource = (TtsPronSource)word.PronSource;

                            sentence.Words.Add(scriptWord);
                        }
                    }

                    sentence.Text = sentence.BuildTextFromWords();
                    item.Sentences.Add(sentence);
                }
            }

            return item;
        }
        /// <summary>
        /// Insert word to the position.
        /// </summary>
        /// <param name="scriptWord">Word to be insert.</param>
        /// <param name="position">Position to be inserted.</param>
        public void InsertWord(ScriptWord scriptWord, int position)
        {
            if (scriptWord == null)
            {
                throw new ArgumentNullException("scriptWord");
            }

            if (position < 0 || position > _words.Count)
            {
                throw new ArgumentException(Helper.NeutralFormat(
                    "Invalid position {0}, should between 0 and {1}.", position, _words.Count));
            }

            _words.Insert(position, scriptWord);
            scriptWord.Sentence = this;
            _needBuildUnits = true;
        }
        /// <summary>
        /// Dump the data in the syllable.
        /// </summary>
        /// <param name="scriptWord">The script word to store the data dumped from the syllables.</param>
        /// <param name="utt">The utterance.</param>
        /// <param name="word">The word which contains the these syllables.</param>
        /// <param name="phoneIndex">Phone index to mark the phone in the Utt.Phones.</param>
        /// <param name="unitIndex">Unit index to mark the unit in the Utt.Units.</param>
        /// <param name="f0StartIndex">F0 index to mark the start position in the F0s.</param>
        /// <param name="ttsEngine">The object ttsEngine to help to convert the Pos and get sentence id.</param>
        private static void DumpSyllables(ScriptWord scriptWord, SP.TtsUtterance utt,
            SP.TtsWord word, ref int phoneIndex, ref int unitIndex, ref int f0StartIndex, SP.TtsEngine ttsEngine)
        {
            Debug.Assert(scriptWord != null, "ScriptWord should not be null");
            Debug.Assert(utt != null, "Utt should not be null");
            Debug.Assert(word != null, "Word should not be null");
            Debug.Assert(phoneIndex >= 0, "PhoneIndex should not be less than 0");
            Debug.Assert(f0StartIndex >= 0, "f0StartIndex should not be less than 0");
            Debug.Assert(ttsEngine != null, "ttsEngine should not be null");

            // Go through each syllable in the word.
            SP.TtsSyllable syllable = word.FirstSyllable;
            while (syllable != null)
            {
                ScriptSyllable scriptSyllable = new ScriptSyllable();
                TtsTobiAccentSet tobiAccentSet = new TtsTobiAccentSet();
                if (syllable.ToBIAccent != SP.TtsTobiAccent.K_NOACC)
                {
                    scriptSyllable.TobiPitchAccent = TobiLabel.Create(tobiAccentSet.IdItems[(uint)syllable.ToBIAccent]);
                }

                scriptSyllable.Stress = (TtsStress)syllable.Stress;
                DumpPhones(scriptSyllable, utt, syllable, ref phoneIndex, ref unitIndex, ref f0StartIndex, ttsEngine);
                scriptWord.Syllables.Add(scriptSyllable);
                if (syllable == word.LastSyllable)
                {
                    break;
                }

                syllable = syllable.Next;
            }
        }
        /// <summary>
        /// Get pronunciation id string for letter domain.
        /// </summary>
        /// <param name="word">Given ScriptWord.</param>
        /// <returns>Pronunciation id string.</returns>
        private static string GetPhoneIds(ScriptWord word)
        {
            Phoneme phoneme = Localor.GetPhoneme(word.Language);
            string[] phones = word.Pronunciation.Split(new char[] { ' ' },
                StringSplitOptions.RemoveEmptyEntries);

            StringBuilder phoneIds = new StringBuilder();
            foreach (string phone in phones)
            {
                if (phone == TtsUnit.UnitDelimiter)
                {
                    continue;
                }

                if (phoneme.TtsPhoneIds.ContainsKey(phone))
                {
                    phoneIds.Append((char)phoneme.TtsPhoneIds[phone]);
                }
            }

            return phoneIds.ToString();
        }