/// <summary>
        /// Compile the trunc rule into binary writer.
        /// </summary>
        /// <param name="truncRuleFileName">File path of trunc rule.</param>
        /// <param name="phoneSet">Phone set.</param>
        /// <param name="bw">Binary writer.</param>
        /// <returns>Error.</returns>
        private static ErrorSet CompTruncRuleData(string truncRuleFileName, TtsPhoneSet phoneSet, BinaryWriter bw)
        {
            // maximum truncate rule length is 5 phonmes currently
            const int MaxTruncRuleLength = 5;
            ErrorSet errorSet = new ErrorSet();
            List<TruncateNucleusRule> rules = new List<TruncateNucleusRule>();

            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(truncRuleFileName);
            XmlNamespaceManager nm = new XmlNamespaceManager(xmldoc.NameTable);
            nm.AddNamespace("tts", "http://schemas.microsoft.com/tts/toolsuite");
            XmlNodeList nodeList = xmldoc.DocumentElement.SelectNodes(
                "/tts:offline/tts:truncateRules/tts:truncateRule", nm);
            if (nodeList != null)
            {
                foreach (XmlNode node in nodeList)
                {
                    XmlNodeList phoneNodeList;
                    XmlElement xmlNode = node as XmlElement;
                    string side = xmlNode.GetAttribute("side");
                    int direction = 0;

                    if (side.Equals("Right", StringComparison.OrdinalIgnoreCase))
                    {
                        direction = 2;  // TruncFromRight
                    }
                    else if (side.Equals("Left", StringComparison.OrdinalIgnoreCase))
                    {
                        direction = 1; // TruncFromLeft
                    }
                    else
                    {
                        errorSet.Add(UnitGeneratorDataCompilerError.WrongRuleSide, 
                            side, xmlNode.InnerXml);
                    }

                    phoneNodeList = xmlNode.SelectNodes("tts:phone", nm);
                    if (phoneNodeList.Count > MaxTruncRuleLength)
                    {
                        errorSet.Add(UnitGeneratorDataCompilerError.RuleLengthExceeded,
                            MaxTruncRuleLength.ToString(CultureInfo.InvariantCulture), xmlNode.InnerXml);
                    }
                    else
                    {
                        int idx = 0;
                        short[] ids = new short[MaxTruncRuleLength + 1];

                        foreach (XmlNode phoneNode in phoneNodeList)
                        {
                            XmlElement xmlPhoneNode = phoneNode as XmlElement;

                            string phoneValue = xmlPhoneNode.GetAttribute("value");
                            Phone phone = phoneSet.GetPhone(phoneValue);
                            if (phone != null)
                            {
                                ids[idx++] = (short)phone.Id;
                            }
                            else
                            {
                                errorSet.Add(UnitGeneratorDataCompilerError.InvalidPhone, phoneValue);
                            }
                        }

                        ids[idx] = 0;
                        TruncateNucleusRule rule = new TruncateNucleusRule();
                        rule.Ids = ids;
                        rule.Direction = direction;
                        rules.Add(rule);
                    }
                }
            }

            // write the data
            bw.Write(rules.Count);
            foreach (TruncateNucleusRule ci in rules)
            {
                bw.Write(ci.Direction);
                for (int i = 0; i < ci.Ids.Length; i++)
                {
                    bw.Write(BitConverter.GetBytes(ci.Ids[i]));
                }
            }

            return errorSet;
        }
        /// <summary>
        /// Build the syllable's pronunciation.
        /// </summary>
        /// <param name="phoneSet">Phone set.</param>
        /// <returns>The built pronunciation string.</returns>
        public string BuildTextFromPhones(TtsPhoneSet phoneSet)
        {
            if (phoneSet == null)
            {
                throw new ArgumentNullException("phoneSet");
            }

            StringBuilder sb = new StringBuilder();

            foreach (ScriptPhone phone in _phones)
            {
                sb.AppendFormat("{0}{1}", phone.Name, " ");

                // append stress
                if (_stress != TtsStress.None)
                {
                    Phone ttsPhone = phoneSet.GetPhone(phone.Name);
                    if (ttsPhone != null && ttsPhone.IsVowel)
                    {
                        switch (_stress)
                        {
                            case TtsStress.Primary:
                                sb.Append("1 ");
                                break;
                            case TtsStress.Secondary:
                                sb.Append("2 ");
                                break;
                            case TtsStress.Tertiary:
                                sb.Append("3 ");
                                break;
                        }
                    }
                }

                // append tone
                if (!string.IsNullOrEmpty(phone.Tone))
                {
                    sb.AppendFormat("{0}{1}", phone.Tone, " ");
                }
            }

            return sb.ToString().Trim();
        }
        /// <summary>
        /// Compiler.
        /// </summary>
        /// <param name="syllabifyRuleFileName">Path of syllabify rule.</param>
        /// <param name="phoneSet">Phone set.</param>
        /// <param name="outputStream">Output Stream.</param>
        /// <returns>ErrorSet.</returns>
        public static ErrorSet Compile(string syllabifyRuleFileName, TtsPhoneSet phoneSet, 
            Stream outputStream)
        {
            if (string.IsNullOrEmpty(syllabifyRuleFileName))
            {
                throw new ArgumentNullException("syllabifyRuleFileName");
            }

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

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

            // maximum rule length is 3 phonmes currently
            const int MaxRuleLength = 3;
            ErrorSet errorSet = new ErrorSet();
            phoneSet.Validate();
            if (phoneSet.ErrorSet.Contains(ErrorSeverity.MustFix))
            {
                errorSet.Add(SyllabifyRuleCompilerError.InvalidPhoneSet);
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(outputStream);
                {
                    List<ushort[]> rules = new List<ushort[]>();

                    XmlDocument xmldoc = new XmlDocument();
                    xmldoc.Load(syllabifyRuleFileName);
                    XmlNamespaceManager nm = new XmlNamespaceManager(xmldoc.NameTable);
                    nm.AddNamespace("tts", _ttsSchemaUri);
                    XmlNodeList nodeList = xmldoc.DocumentElement.SelectNodes(
                        "/tts:syllabifyRules/tts:initialConsonants", nm);

                    if (nodeList != null)
                    {
                        foreach (XmlNode node in nodeList)
                        {
                            XmlNodeList phoneNodeList;
                            XmlElement xmlNode = node as XmlElement;

                            phoneNodeList = xmlNode.SelectNodes("tts:phone", nm);
                            if (phoneNodeList.Count > MaxRuleLength)
                            {
                                errorSet.Add(SyllabifyRuleCompilerError.RuleLengthExceeded,
                                    MaxRuleLength.ToString(CultureInfo.InvariantCulture), xmlNode.InnerXml);
                            }
                            else
                            {
                                ushort[] rule = new ushort[MaxRuleLength + 1];
                                int idx = 0;

                                foreach (XmlNode phoneNode in phoneNodeList)
                                {
                                    XmlElement xmlPhoneNode = phoneNode as XmlElement;

                                    string phoneValue = xmlPhoneNode.GetAttribute("value");
                                    Phone phone = phoneSet.GetPhone(phoneValue);
                                    if (phone != null)
                                    {
                                        rule[idx++] = (ushort)phone.Id;
                                    }
                                    else
                                    {
                                        errorSet.Add(SyllabifyRuleCompilerError.InvalidPhone, phoneValue);
                                    }
                                }

                                rule[idx] = 0;
                                rules.Add(rule);
                            }
                        }

                        bw.Write(rules.Count);
                        foreach (ushort[] ci in rules)
                        {
                            for (int i = 0; i < ci.Length; i++)
                            {
                                bw.Write(BitConverter.GetBytes(ci[i]));
                            }
                        }
                    }
                }
            }

            return errorSet;
        }
        /// <summary>
        /// Check if the phone in phone question is in tts phone set.
        /// </summary>
        /// <param name="phoneSet">TTS Phone set.</param>
        /// <returns>Errors.</returns>
        public ErrorSet Validate(TtsPhoneSet phoneSet)
        {
            ErrorSet errors = new ErrorSet();
            foreach (string phone in _phones)
            {
                if (phoneSet.GetPhone(phone) == null)
                {
                    errors.Add(PhoneQuestionErrorType.UnrecognizedPhone, phone, _name);
                }
            }

            return errors;
        }