Exemplo n.º 1
0
        /// <summary>
        /// Backlog記法の文字列をMarkdown記法に変換する
        /// </summary>
        /// <param name="backlogString"></param>
        /// <returns></returns>
        public string ToMarkdown(string backlogString)
        {
            setLayoutPatterns();

            string        result     = backlogString;
            List <string> codes      = new List <string>();
            List <string> quotes     = new List <string>();
            List <string> paragraphs = new List <string>();

            //Windows用改行コード(\r\n)を\nのみに変換
            result = result.Replace("\r\n", "\n");

            // 正規表現を助けるための改行を追加
            result = "\n" + result + "\n\n";

            // 目次
            result =
                new RegexMatchReplacer()
            {
                RegexOptions = RegexOptions.Multiline,
                Pattern      = @"^#contents$",
                Formatter    = match =>
                {
                    var ret = "[toc]\n";
                    return(ret);
                }
            }.Replace(result);

            // コード範囲指定を隠す
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "\n{code}(?<p1>[\\s|\\S]*?){\\/code}\n",
                Formatter = match =>
                {
                    var p1 = match.Groups["p1"].Value;
                    codes.Add(p1);
                    var ret = $"\n{{{{CODE_REPACE_BACKLOG_TO_MARKDOWN-{codes.Count - 1}}}}}\n";
                    return(ret);
                }
            }.Replace(result);

            // 引用範囲指定を隠す
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "\n{quote}(?<p1>[\\s|\\S]*?){\\/quote}\n",
                Formatter = match =>
                {
                    var p1 = match.Groups["p1"].Value;
                    quotes.Add(p1);
                    var ret = $"\n{{{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-{quotes.Count - 1}}}}}\n";
                    return(ret);
                }
            }.Replace(result);

            // 通常テキスト(パラグラフ)を隠す
            result =
                new RegexMatchReplacer()
            {
                RegexOptions = RegexOptions.Multiline,
                Pattern      = "^.*$",
                Formatter    = match =>
                {
                    string isP = "^(?![*\\|\\-\\+\\s>)`])(.*)$";
                    var    p1  = match.Value;

                    if (!string.IsNullOrEmpty(p1) &&
                        Regex.IsMatch(p1, isP) &&
                        !p1.StartsWith("{{CODE_REPACE_BACKLOG_TO_MARKDOWN") &&
                        !p1.StartsWith("{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN"))
                    {
                        paragraphs.Add(p1);
                        return($"{{{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-{paragraphs.Count - 1}}}}}");
                    }
                    else
                    {
                        return(p1);
                    }
                }
            }.Replace(result);

            // パラグラフの塊は最後に空行を開けさせる
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "\n{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-.*?}}\n(?!{{)",
                Formatter = match =>
                {
                    var ret = $"{match.Value}\n";
                    return(ret);
                }
            }.Replace(result);

            // 範囲指定型以外のBacklog記法を置き換える
            foreach (var r in layoutPatterns)
            {
                result = r.Replace(result);
            }

            // 範囲指定系を埋めもどす前に無駄な改行を削除する
            while (Regex.IsMatch(result, "\n\n\n"))
            {
                result = Regex.Replace(result, "\n\n\n", "\n\n");
            }

            // コード範囲指定を戻す
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "{{CODE_REPACE_BACKLOG_TO_MARKDOWN-(?<p1>.*?)}}",
                Formatter = match =>
                {
                    var p1      = match.Groups["p1"].Value;
                    var index   = int.Parse(p1);
                    var content = codes[index].Trim();
                    var ret     = !string.IsNullOrEmpty(content) ?
                                  $"\n```\n" + content + "\n```\n" :
                                  "\n```\n```\n";
                    return(ret);
                }
            }.Replace(result);

            // 引用範囲指定を戻す
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-(?<p1>.*?)}}",
                Formatter = match =>
                {
                    var p1      = match.Groups["p1"].Value;
                    var index   = int.Parse(p1);
                    var content = quotes[index].Trim().Replace("\n", "\n> ");
                    var ret     = $"\n> " + content + "\n";
                    return(ret);
                }
            }.Replace(result);

            // パラグラフを戻す
            result =
                new RegexMatchReplacer()
            {
                Pattern   = "{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-(?<p1>.*?)}}",
                Formatter = match =>
                {
                    var p1      = match.Groups["p1"].Value;
                    var index   = int.Parse(p1);
                    var content = textLevelSemanticsCheck(paragraphs[index].Trim());
                    var ret     = content;
                    return(ret);
                }
            }.Replace(result);


            return(convertLf(result.Trim()));
        }
Exemplo n.º 2
0
        /// <summary>
        /// <see cref="layoutPatterns"/>に内容をセット
        /// </summary>
        void setLayoutPatterns()
        {
            layoutPatterns = new List <IReplacer>()
            {
                // 改行コード
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\r",
                    Formatter = match =>
                    {
                        var result = $"\n";
                        return(result);
                    }
                },
                new RegexMatchReplacer()
                {
                    Pattern   = "\r",
                    Formatter = match =>
                    {
                        var result = $"\n";
                        return(result);
                    }
                },

                // 見出し
                new RegexMatchReplacer()
                {
                    RegexOptions = RegexOptions.Multiline,
                    Pattern      = "^(?<p1>\\*+)(?<p2>.*)$",
                    Formatter    = match =>
                    {
                        var p1 = match.Groups["p1"].Value;
                        var p2 = match.Groups["p2"].Value;

                        var formattedP1 = Regex.Replace(p1, "\\*", "#");

                        //var result = input.Replace(match.Value, $"\n{formattedP1} {textLevelSemanticsCheck(p2.Trim())}\n");
                        var result = $"\n{formattedP1} {textLevelSemanticsCheck(p2.Trim())}\n";
                        return(result);
                    }
                },

                // theadなしテーブル
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\|(?<p1>[\\s|\\S]*?)\\|\n(?!\\|)",
                    Formatter = match =>
                    {
                        var p1     = match.Groups["p1"].Value;
                        var result = $"\n|{p1}|\n\n";
                        return(result);
                    }
                },
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\|(?<p1>[\\s|\\S]*?)\\|\n(?!\\|)",
                    Formatter = match =>
                    {
                        var p1 = match.Groups["p1"].Value;

                        if (!p1.Contains("|h\n"))
                        {
                            int    i   = 0;
                            int    max = p1.Split('\n')[0].Split('|').Length;
                            string row = "";

                            for (i = 1; i < max; i++) //maxには最終の空白も含めたlengthが入るので、1つ減らすためにi = 1から開始している
                            {
                                row += "|:--";
                            }

                            row += "|";
                            return($"\n{row}\n|{p1}|\n");
                        }

                        return($"\n|{p1}|\n");
                    }
                },

                // 行見出しテーブル
                new RegexMatchReplacer()
                {
                    RegexOptions = RegexOptions.Multiline,
                    Pattern      = "^\\|(?<p1>.*)\\|(?<p2>\\s?)$",
                    Formatter    = match =>
                    {
                        var p1 = match.Groups["p1"].Value;
                        var p2 = match.Groups["p2"].Value;

                        p1 = Regex.Replace(p1, "\\|~", "|");
                        p1 = Regex.Replace(p1, "^~", "");
                        p1 = Regex.Replace(p1, "\\|\\|", "| |");
                        p1 = Regex.Replace(p1, "^\\|", " |");
                        p1 = Regex.Replace(p1, "^\\|", " |");   // 先頭が空
                        p1 = Regex.Replace(p1, "\\|$", "| ");   // 最後が空

                        var result = $"|{p1}|{p2}";
                        return(result);
                    }
                },
                // 列見出しテーブル
                new RegexMatchReplacer()
                {
                    RegexOptions = RegexOptions.Multiline,
                    Pattern      = "^\\|(?<p1>.*)\\|h\\s?$",
                    Formatter    = match =>
                    {
                        var p1 = match.Groups["p1"].Value;

                        string row  = "";
                        var    cell = p1.Split('|');
                        int    i    = 0;
                        int    max  = cell.Length;

                        for (i = 0; i < max; i++)
                        {
                            row += "|:--";
                        }

                        row += '|';

                        p1 = Regex.Replace(p1, "\\|~", "|");
                        p1 = Regex.Replace(p1, "^~", "");
                        p1 = Regex.Replace(p1, "\\|\\|", "| |");
                        p1 = Regex.Replace(p1, "^\\|", " |");   // 先頭が空
                        p1 = Regex.Replace(p1, "\\|$", "| ");   // 最後が空

                        var result = $"\n|{p1}|\n{row}";
                        return(result);
                    }
                },

                // テーブルに改行
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\|(?<p1>[\\s|\\S]*?)\\|\n(?<p2>[^|])",
                    Formatter = match =>
                    {
                        var p1     = match.Groups["p1"].Value;
                        var p2     = match.Groups["p2"].Value;
                        var result = $"\n|{textLevelSemanticsCheck(p1)}|\n\n{p2}";
                        return(result);
                    }
                },
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\|(?<p1>[\\s|\\S]*?)\\|\n(?!\\|)",
                    Formatter = match =>
                    {
                        var p1     = match.Groups["p1"].Value;
                        var result = $"\n\n|{p1}|\n\n";
                        return(result);
                    }
                },
                new RegexMatchReplacer()    //追加 : ヘッダーがないテーブルの処理
                {
                    RegexOptions = RegexOptions.Multiline,
                    Pattern      = "^\n(?<p1>\\|:.*)\n(?<p2>.*$)", //空改行→アラインメント行→1行目
                    Formatter    = match =>
                    {
                        var p1 = match.Groups["p1"].Value;  //アラインメント行
                        var p2 = match.Groups["p2"].Value;  //1行目

                        if (ForceMakeFirstLineHeader)
                        {
                            //アラインメント行と1行目を入れ替える
                            return($"\n{p2}\n{p1}");
                        }
                        else
                        {
                            //空のヘッダー行をアラインメント行の上に挿入
                            string blankHeader = Regex.Replace(p1, ":-*", string.Empty);
                            return($"\n{blankHeader}\n{p1}\n{p2}");
                        }
                    }
                },


                // 順序リスト
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\+(?<p1>[\\s|\\S]*?)\n\n",
                    Formatter = match =>
                    {
                        var p1     = match.Groups["p1"].Value;
                        var result = $"\n+{p1}\n\n\n";
                        return(result);
                    }
                },
                new RegexMatchReplacer()
                {
                    Pattern   = "\n\\+(?<p1>[\\s|\\S]*?)\n\n",
                    Formatter = match =>
                    {
                        var p1 = match.Groups["p1"].Value;

                        p1 = "\n+" + p1.Trim();
                        // スペースの整形
                        var replacer = new RegexMatchReplacer()
                        {
                            RegexOptions = RegexOptions.Multiline,
                            Pattern      = "^(?<p2>\\++)(?<p3>.*)$",
                            Formatter    = m =>
                            {
                                var p2 = m.Groups["p2"].Value;
                                var p3 = m.Groups["p3"].Value;

                                return($"{p2} {p3.Trim()}");
                            }
                        };
                        p1 = replacer.Replace(p1);
                        p1 = p1.Trim();

                        Func <string, string> lineFormat = str =>
                        {
                            var ret = string.Empty;

                            Dictionary <int, int> symbolCount = new Dictionary <int, int>();  // Keyはインデントレベル。インデントが上がったら
                            int currentLevel = 0;

                            foreach (string line in str.Split('\n'))
                            {
                                int level = line.Split(' ')[0].Length;

                                if (level < currentLevel)
                                {
                                    symbolCount.Add(currentLevel, 0);
                                }

                                currentLevel = level;
                                if (!symbolCount.ContainsKey(currentLevel))
                                {
                                    symbolCount.Add(currentLevel, 1);
                                }
                                else
                                {
                                    symbolCount[currentLevel]++;
                                }

                                ret += textLevelSemanticsCheck(line.Replace("+ ", symbolCount[currentLevel].ToString() + ". ")) + "\n";
                            }

                            return(ret);
                        };
                        p1 = lineFormat.Invoke(p1);

                        // ネストがあるときに残っている + をスペースに変換
                        var indentReplacer = new RegexMatchReplacer()
                        {
                            RegexOptions = RegexOptions.Multiline,
                            Pattern      = "^(?<p4>\\++)(?<p5>.*)",
                            Formatter    = m =>
                            {
                                var p4 = m.Groups["p4"].Value;
                                var p5 = m.Groups["p5"].Value;

                                int    max    = p4.Length;
                                string indent = "";

                                for (var i = 0; i < max; i++)
                                {
                                    indent += "    ";
                                }

                                return(indent + p5);
                            }
                        };
                        p1 = indentReplacer.Replace(p1);

                        var result = $"\n{p1}\n";
                        return(result);
                    }
                },

                // 非順序リスト
                new RegexMatchReplacer()
                {
                    RegexOptions = RegexOptions.Multiline,
                    Pattern      = "^(?<p1>-+)(?<p2>.*)$",
                    Formatter    = match =>
                    {
                        var p1 = match.Groups["p1"].Value;
                        var p2 = match.Groups["p2"].Value;

                        int    i      = 0;
                        int    max    = p1.Length - 1;
                        string indent = "";

                        if (string.IsNullOrEmpty(p2))
                        {
                            return(p1);  // hr要素
                        }

                        for (i = 0; i < max; i++)
                        {
                            indent += "    ";
                        }

                        indent += "-";
                        p2      = textLevelSemanticsCheck(p2).Trim();

                        var result = $"{indent} {p2}";
                        return(result);
                    }
                },


                // 改行をbr要素に変換(Markdown in Backlogでは無視されます)
                new RegexReplacer("&br;", " <br>"),
                new RegexReplacer("&", "&amp;")
            };
        }