public void Insert(int index, BrailleWord brWord)
 {
     m_Words.Insert(index, brWord);
 }
        private bool m_IsEngPhonetic;                           // 是否為英語音標(用來判斷不要加空方).

        //private bool m_QuotationResolved;	// 是否已經識別出左右引號(英文的單引號和雙引號都是同一個符號,但點字不同)

        static BrailleWord()
        {
            m_Blank = BrailleWord.NewBlank();
        }
        /// <summary>
        /// 轉換分數。
        /// </summary>
        /// <param name="lineNumber"></param>
        /// <param name="chars"></param>
        /// <returns></returns>
        private List <BrailleWord> ConvertFraction(int lineNumber, Stack <char> chars)
        {
            char[] charAry = chars.ToArray();
            string s       = new string(charAry);
            int    idxEof  = s.IndexOf(ContextTag.GetEndTagName(ContextTagNames.Fraction));             // end of fraction

            if (idxEof < 0)
            {
                throw new Exception("<分數> 標籤有起始但沒有結束標籤!");
            }

            s = s.Substring(0, idxEof);                 // 從字串頭取到 </分數> 標籤之前。

            string intPart;
            string numerator;
            string denumerator;

            ParseFraction(s, out intPart, out numerator, out denumerator);

            // Note: 整數部份、分子、分母都有可能是英文字母(代數)。

            string             temp;
            Stack <char>       charStack;
            List <BrailleWord> brWordList = null;

            List <BrailleWord> brWordListIntPart = new List <BrailleWord>();

            if (!String.IsNullOrEmpty(intPart))
            {
                // 將整數部份轉換成點字串列。
                temp      = StrHelper.Reverse(intPart);
                charStack = new Stack <char>(temp);

                while (charStack.Count > 0)
                {
                    brWordList = ConvertWord(charStack);
                    if (brWordList == null)
                    {
                        throw new Exception("無法轉換分數的整數部份!");
                    }
                    else
                    {
                        brWordListIntPart.AddRange(brWordList);
                    }
                }
            }

            // 將分子部份轉換成點字串列。
            temp      = StrHelper.Reverse(numerator + "/");
            charStack = new Stack <char>(temp);
            List <BrailleWord> brWordListNumerator = new List <BrailleWord>();

            while (charStack.Count > 0)
            {
                brWordList = ConvertWord(charStack);
                if (brWordList == null)
                {
                    throw new Exception("無法轉換分數的分子部份!");
                }
                else
                {
                    brWordListNumerator.AddRange(brWordList);
                }
            }
            // 分子的數字不要加數符
            foreach (BrailleWord brWord in brWordListNumerator)
            {
                brWord.NoDigitCell = true;
            }
            // 補上分子的點字符號
            brWordListNumerator[0].Cells.Insert(0, BrailleCell.GetInstance(new int[] { 1, 4, 5, 6 }));
            if (brWordListIntPart.Count > 0)
            {
                brWordListNumerator[0].Cells.Insert(0, BrailleCell.GetInstance(new int[] { 4, 5, 6 }));
            }

            // 將分母部份轉換成點字串列。
            temp      = StrHelper.Reverse(denumerator);
            charStack = new Stack <char>(temp);
            List <BrailleWord> brWordListDenumerator = new List <BrailleWord>();

            while (charStack.Count > 0)
            {
                brWordList = ConvertWord(charStack);
                if (brWordList == null)
                {
                    throw new Exception("無法轉換分數的分母部份!");
                }
                else
                {
                    brWordListDenumerator.AddRange(brWordList);
                }
            }
            // 分母的數字不要加數符
            foreach (BrailleWord brWord in brWordListDenumerator)
            {
                brWord.NoDigitCell = true;
            }

            // 補上分母後面的點字符號
            BrailleWord lastBrWord = brWordListDenumerator[brWordListDenumerator.Count - 1];

            if (brWordListIntPart.Count > 0)
            {
                lastBrWord.Cells.Add(BrailleCell.GetInstance(new int[] { 4, 5, 6 }));
            }
            lastBrWord.Cells.Add(BrailleCell.GetInstance(new int[] { 3, 4, 5, 6 }));

            // 結合整數部份、分子、分母至同一個串列。
            List <BrailleWord> brWordListFraction = new List <BrailleWord>();

            brWordListFraction.AddRange(brWordListIntPart);
            brWordListFraction.AddRange(brWordListNumerator);
            brWordListFraction.AddRange(brWordListDenumerator);

            // 完成! 從傳入的字元堆疊中取出已經處理的字元。
            while (idxEof > 0)
            {
                chars.Pop();
                idxEof--;
            }

            return(brWordListFraction);
        }
        /// <summary>
        /// 把一行明眼字串轉換成點字串列。
        /// 此時不考慮一行幾方和斷行的問題,只進行單純的轉換。
        /// 斷行由其他函式負責處理,因為有些點字規則必須在斷行時才能處理。
        /// </summary>
        /// <param name="lineNumber">字串的行號。此參數只是用來當轉換失敗時,傳給轉換失敗事件處理常式的資訊。</param>
        /// <param name="line">輸入的明眼字串。</param>
        /// <param name="isTitle">輸出參數,是否為標題。</param>
        /// <returns>點字串列。若則傳回 null,表示該列不需要轉成點字。</returns>
        public BrailleLine ConvertLine(int lineNumber, string line)
        {
            if (line == null)
            {
                return(null);
            }

            BrailleLine brLine = new BrailleLine();

            string orgLine = line;              // 保存原始的字串。

            // 把換行符號之後的字串去掉
            int i = line.IndexOfAny(new char[] { '\r', '\n' });

            if (i >= 0)
            {
                line = line.Substring(0, i);
            }

            // 若去掉換行字元之後變成空字串,則傳回只包含一個空方的列。
            if (String.IsNullOrEmpty(line))
            {
                brLine.Words.Add(BrailleWord.NewBlank());
                return(brLine);
            }

            // 預先處理特殊標籤的字元替換。
            line = PreprocessTagsForLine(line);
            if (line == null)
            {
                return(null);
            }

            // 如果是原書頁碼,先檢查格式是否正確。
            try
            {
                GetOrgPageNumber(line);
            }
            catch (Exception ex)
            {
                m_ErrorMsg.Append(String.Format("第 {0} 列 : ", lineNumber));
                m_ErrorMsg.Append(ex.Message);
                m_ErrorMsg.Append("\r\n");
                return(null);
            }

            line = StrHelper.Reverse(line);
            Stack <char> charStack = new Stack <char>(line);

            char ch;
            List <BrailleWord> brWordList;
            StringBuilder      text = new StringBuilder();

            ConvertionFailedEventArgs cvtFailedArgs = new ConvertionFailedEventArgs();
            TextConvertedEventArgs    textCvtArgs   = new TextConvertedEventArgs();

            while (charStack.Count > 0)
            {
                brWordList = ConvertWord(charStack);

                if (brWordList != null && brWordList.Count > 0)
                {
                    // 成功轉換成點字,有 n 個字元會從串流中取出
                    brLine.Words.AddRange(brWordList);

                    text.Length = 0;
                    foreach (BrailleWord brWord in brWordList)
                    {
                        text.Append(brWord.Text);
                    }
                    textCvtArgs.SetArgValues(lineNumber, text.ToString());
                    OnTextConverted(textCvtArgs);
                }
                else
                {
                    // 無法判斷和處理的字元應該會留存在串流中,將之取出。
                    ch = charStack.Pop();

                    int charIndex = line.Length - charStack.Count;

                    // 引發事件。
                    cvtFailedArgs.SetArgs(lineNumber, charIndex, orgLine, ch);
                    OnConvertionFailed(cvtFailedArgs);
                    if (cvtFailedArgs.Stop)
                    {
                        break;
                    }
                }

                // 如果進入分數情境,就把整個分數處理完。
                if (m_ContextTagManager.IsActive(ContextTagNames.Fraction))
                {
                    try
                    {
                        brWordList = ConvertFraction(lineNumber, charStack);
                        if (brWordList != null && brWordList.Count > 0)
                        {
                            // 成功轉換成點字,有 n 個字元會從串流中取出
                            brLine.Words.AddRange(brWordList);
                        }
                    }
                    catch (Exception ex)
                    {
                        m_ErrorMsg.Append(String.Format("第 {0} 列 : ", lineNumber));
                        m_ErrorMsg.Append(ex.Message);
                        m_ErrorMsg.Append("\r\n");
                    }
                }
            }

            ChineseBrailleRule.ApplyPunctuationRules(brLine);   // 套用中文標點符號規則。

            // 不刪除多餘空白,因為原本輸入時可能就希望縮排。
            //ChineseBrailleRule.ShrinkSpaces(brLine);	// 把連續全形空白刪到只剩一個。

            // 將編號的數字修正成上位點。
            if (m_EnglishConverter != null)
            {
                EnglishBrailleRule.FixNumbers(brLine, m_EnglishConverter.BrailleTable as EnglishBrailleTable);
            }

            EnglishBrailleRule.ApplyCapitalRule(brLine);    // 套用大寫規則。
            EnglishBrailleRule.ApplyDigitRule(brLine);      // 套用數字規則。
            EnglishBrailleRule.AddSpaces(brLine);           // 補加必要的空白。

            ChineseBrailleRule.ApplyBracketRule(brLine);    // 套用括弧規則。

            // 不刪除多於空白,因為原本輸入時可能就希望縮排。
            //EnglishBrailleRule.ShrinkSpaces(brLine);        // 把連續空白刪到只剩一個。

            return(brLine);
        }
        /// <summary>
        /// 將一行點字串列斷成多行。
        /// </summary>
        /// <param name="brLine">來源點字串列。</param>
        /// <param name="cellsPerLine">每行最大方數。</param>
        /// <param name="context">情境物件。</param>
        /// <returns>斷行之後的多行串列。若為 null 表示無需斷行(指定的點字串列未超過每行最大方數)。</returns>
        public List <BrailleLine> BreakLine(BrailleLine brLine, int cellsPerLine, ContextTagManager context)
        {
            if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
            {
                // 每列最大方數要扣掉縮排數量,並於事後補縮排的空方。
                // NOTE: 必須在斷行之後才補縮排的空方!
                cellsPerLine -= context.IndentCount;
            }

            // 若指定的點字串列未超過每行最大方數,則無須斷行,傳回 null。
            if (brLine.CellCount <= cellsPerLine)
            {
                // 補縮排的空方。
                if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
                {
                    this.Indent(brLine, context.IndentCount);
                }
                return(null);
            }

            List <BrailleLine> lines   = new List <BrailleLine>();
            BrailleLine        newLine = null;
            int  wordIndex             = 0;
            int  breakIndex            = 0;
            bool needHyphen            = false;
            bool isBroken = false; // 是否已經斷行了?
            int  indents  = 0;     // 第一次斷行時,不會有系統自斷加上的縮排,因此初始為 0。
            int  maxCells = cellsPerLine;;

            // 計算折行之後的縮排格數。
            indents = BrailleProcessor.CalcNewLineIndents(brLine);

            while (wordIndex < brLine.WordCount)
            {
                breakIndex = BrailleProcessor.CalcBreakPoint(brLine, maxCells, out needHyphen);

                newLine = brLine.Copy(wordIndex, breakIndex); // 複製到新行。
                if (needHyphen)                               // 是否要附加連字號?
                {
                    newLine.Words.Add(new BrailleWord("-", BrailleCellCode.Hyphen));
                }
                newLine.TrimEnd();                      // 去尾空白。

                // 如果是折下來的新行,就自動補上需要縮排的格數。
                if (isBroken)
                {
                    for (int i = 0; i < indents; i++)
                    {
                        newLine.Insert(0, BrailleWord.NewBlank());
                    }
                }

                brLine.RemoveRange(0, breakIndex);                                              // 從原始串列中刪除掉已經複製到新行的點字。
                wordIndex = 0;
                lines.Add(newLine);

                // 防錯:檢驗每個斷行後的 line 的方數是否超過每列最大方數。
                // 若超過,即表示之前的斷行處理有問題,須立即停止執行,否則錯誤會
                // 直到在雙視編輯的 Grid 顯示時才出現 index out of range,不易抓錯!
                System.Diagnostics.Debug.Assert(newLine.CellCount <= cellsPerLine, "斷行錯誤! 超過每列最大方數!");

                // 被折行之後的第一個字需要再根據規則調整。
                EnglishBrailleRule.ApplyCapitalRule(brLine); // 套用大寫規則。
                EnglishBrailleRule.ApplyDigitRule(brLine);   // 套用數字規則。

                isBroken = true;                             // 已經至少折了一行
                maxCells = cellsPerLine - indents;           // 下一行開始就要自動縮排,共縮 indents 格。
            }

            // 補縮排的空方。
            if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
            {
                indents = context.IndentCount;
                foreach (BrailleLine aLine in lines)
                {
                    this.Indent(aLine, indents);
                }
            }

            return(lines);
        }
Beispiel #6
0
        /// <summary>
        /// 根據前後鄰近的字元判斷中間是否需要加一個空方。
        /// </summary>
        /// <param name="lastWord">前一個字。</param>
        /// <param name="currWord">目前的字。</param>
        /// <returns></returns>
        private static bool NeedSpace(BrailleWord lastWord, BrailleWord currWord)
        {
            if (lastWord == null || currWord == null)
            {
                throw new ArgumentException("傳入 NeedSpace() 的參數為 null!");
            }

            if (lastWord.IsEngPhonetic && currWord.IsEngPhonetic)
            {
                return(false);
            }

            if (String.IsNullOrEmpty(lastWord.Text) || String.IsNullOrEmpty(currWord.Text))
            {
                return(false);
            }

            if (ContextTag.StartsWithContextTag(lastWord.Text))                 // 如果前一個字是情境標籤,就不加空方
            {
                return(false);
            }
            if (ContextTag.StartsWithContextTag(currWord.Text))                 // 如果目前的字是情境標籤,就不加空方
            {
                return(false);
            }

            char lastChar = lastWord.Text[0];
            char currChar = currWord.Text[0];

            // 若前一個字元已經是空白,就不用處理。
            if (Char.IsWhiteSpace(lastChar))
            {
                return(false);
            }

            if (lastChar == '★')  // 「點譯者註」的後面不加空方。
            {
                return(false);
            }

            if (lastChar == '□' || currChar == '□')  // 「滿點」符號的左右均不加空方。
            {
                return(false);
            }

            if (CharHelper.IsAscii(currChar) && !CharHelper.IsAscii(lastChar))
            {
                // 目前的字元是 ASCII,但前一個字元不是(視為中文或其他雙位元組字元),需插入
                // 一個空白,除了一些例外,如:全型+號前後若接數字,即視為數學式子,+號前後都不空方。

                if (Char.IsDigit(currChar))
                {
                    switch (lastChar)
                    {
                    case '+':
                    case '-':
                    case '×':       // 全型乘號 (VS2005 編輯器無法正確顯示)
                    case '÷':       // 全型除號 (VS2005 編輯器無法正確顯示)
                    case '(':
                    case '【':       // // 用粗中刮弧把數字包起來時,代表題號,不用加空方.
                        return(false);
                    }
                }
                return(true);
            }

            if (!CharHelper.IsAscii(currChar) && CharHelper.IsAscii(lastChar))
            {
                // 目前的字元不是 ASCII,但前一個字元是,需插入一個空白,
                // 除了一些例外,例如:12℃ 的溫度符號前面不加空方。

                if (Char.IsDigit(lastChar))
                {
                    switch (currChar)
                    {
                    case '∘':      // 溫度符號
                    case '℃':
                    case '+':
                    case '-':
                    case '×':       // 全型乘號 (VS2005 編輯器無法正確顯示)
                    case '÷':       // 全型除號 (VS2005 編輯器無法正確顯示)
                    case ')':
                    case '】':       // 用粗中刮弧把數字包起來時,代表題號,不用加空方.
                        return(false);
                    }
                }
                return(true);
            }
            return(false);
        }
        /// <summary>
        /// 根據標點符號規則調整一整行點字。
        /// </summary>
        /// <param name="brLine"></param>
        public static void ApplyPunctuationRules(BrailleLine brLine)
        {
            int         wordIdx = 0;
            BrailleWord brWord;
            string      text;

            while (wordIdx < brLine.WordCount)
            {
                brWord = brLine[wordIdx];
                if (brWord.Language != BrailleLanguage.Chinese)
                {
                    wordIdx++;
                    continue;
                }

                text = brWord.Text;
                if (text.Length == 3)
                {
                    // 判斷是否為特殊的編號(選擇題的答案編號)
                    if (text[0] == '【' && Char.IsDigit(text[1]) && text[2] == '】')
                    {
                        // 前後需各加一空方。
                        //wordIdx += EncloseBlankCells(brLine, wordIdx) + 1;
                        continue;
                    }
                }
                // 處理緊鄰的"()",在中間加一空方,使其變成"( )"。
                if ("(".Equals(text) && ((wordIdx + 1) < brLine.WordCount))
                {
                    BrailleWord brWord2 = brLine[wordIdx + 1];
                    if (")".Equals(brWord2.Text))
                    {
                        // 兩個緊鄰的點字:"()",要在左括弧前面加一空方,且括弧中間要加一空方,
                        // 否則組成的點字會跟 '○' 的點字一樣。
                        int offset = EncloseBlankCells(brLine, wordIdx);

                        // 設定 '(' 後面跟著的空方和 ')' 為不可斷行。
                        brLine[wordIdx + 1].DontBreakLineHere = true;
                        brLine[wordIdx + 2].DontBreakLineHere = true;

                        wordIdx += offset + 1;
                        continue;
                    }
                    if (BrailleWord.IsBlank(brWord2) && ((wordIdx + 2) < brLine.WordCount))
                    {
                        BrailleWord brWord3 = brLine[wordIdx + 2];
                        if (")".Equals(brWord3.Text))
                        {
                            // 原本輸入的文字就已經在 '(' 和 ')' 中間加了空方。
                            // 設定 '(' 後面跟著的空方和 ')' 為不可斷行。
                            brLine[wordIdx + 1].DontBreakLineHere = true;
                            brLine[wordIdx + 2].DontBreakLineHere = true;

                            int offset = PrefixBlankCell(brLine, wordIdx);

                            wordIdx += offset + 2;
                            continue;
                        }
                    }
                    // 註:左括弧後面跟著的既不是空白也不是右括弧,視為括弧中有文字,
                    // 規則為:左括弧前方不空方,括弧中間不空方,右括弧後面要空方。
                    // 由於無論什麼情況,右括弧後面一定要加空方,因此由下面的程式碼處理,
                    // 這裡無須搜尋對應的右括弧並加空方。
                }

                switch (text)
                {
                case "。":
                case "」":      // 下引號的規則同句號。
                    wordIdx += ApplyPeriodRule(brLine, wordIdx);
                    break;

                case ",":
                    wordIdx += ApplyCommaRule(brLine, wordIdx);
                    break;

                case ":":
                    wordIdx += ApplyColonRule(brLine, wordIdx);
                    break;

                case "!":      // 驚嘆號、問號的規則相同。
                case "?":
                    wordIdx += ApplyQuestionRule(brLine, wordIdx);
                    break;

                case ")":      // 後面要加一空方。
                case "∘":      // 溫度符號
                case "℃":
                    wordIdx += PostfixBlankCell(brLine, wordIdx);
                    break;

                case "※":      // 前後要加一空方。
                case "◎":
                case "○":
                case "╳":
                case "←":
                case "→":
                case "↑":
                case "↓":
                case "∴":
                case "∵":
                case "=":
                case "≠":
                case "<":
                case ">":
                case "≦":
                case "≧":
                case "∠":
                case "⊥":
                    wordIdx += EncloseBlankCells(brLine, wordIdx);
                    break;

                case "√":       // 打勾符號,前後須加空方,後面接逗號時需特殊處理。
                    if ((wordIdx + 1) < brLine.WordCount)
                    {
                        string text2 = brLine[wordIdx + 1].Text;
                        if (text2.Equals(",") || text2.Equals(","))
                        {
                            // 打勾符號後面若跟著逗號,則可連書,且該逗號需用第 6 點。
                            brLine[wordIdx + 1].CellList.Clear();
                            brLine[wordIdx + 1].CellList.Add(BrailleCell.GetInstance(new int[] { 6 }));
                            wordIdx += PrefixBlankCell(brLine, wordIdx);
                        }
                        else
                        {
                            // 否則打勾符號前後
                            wordIdx += EncloseBlankCells(brLine, wordIdx);
                        }
                    }
                    else
                    {
                        wordIdx += EncloseBlankCells(brLine, wordIdx);
                    }
                    break;

                case "▲":       // 刪除符號起始
                    wordIdx += PrefixBlankCell(brLine, wordIdx);
                    break;

                case "▼":       // 刪除符號結束
                    wordIdx += PostfixBlankCell(brLine, wordIdx);
                    break;

                default:
                    break;
                }

                wordIdx++;
            }
        }