JavaScript實現一個簡單的Markdown語法解析器

TANKING發表於2023-03-26

什麼是markdown

Markdown 是一種輕量級標記語言,創始人為約翰·格魯伯(John Gruber)。 它允許人們使用易讀易寫的純文字格式編寫文件,然後轉換成有效的 XHTML(或者HTML)文件。這種語言吸收了很多在電子郵件中已有的純文字標記的特性。

由於 Markdown 的輕量化、易讀易寫特性,並且對於圖片,圖表、數學式都有支援,許多網站都廣泛使用 Markdown 來撰寫幫助文件或是用於論壇上發表訊息。 如 GitHubRedditDiasporaStack ExchangeOpenStreetMapSourceForge、簡書等,甚至還能被使用來撰寫電子書。現在我們所看的 segmentfault 的編輯器也是支援markdown語法的!

上程式碼

</!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
    <script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
    <style>
        *{
            padding: 0;
            margin: 0;
            font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
        }
        #app{
            width: 1610px;
            height: 800px;
            margin: 30px auto 0;
            padding: 10px 10px;
            background: #eee;
        }
        #app .md-editor{
            width: 800px;
            height: 800px;
            float: left;
        }
        #app .md-content{
            width: 100%;
            height: 800px;
            outline: none;
            resize: none;
            padding: 10px 10px;
            font-size: 16px;
            border: none;
            background: #fff;
            color: #666;
        }
        #app .md-html{
            width: 780px;
            height: 780px;
            float: right;
            background: #fff;
            padding: 10px 10px;
            line-height: 20px;
        }
        #app .md-html .placeholder{
            font-size: 16px;
            color: #666;
        }
        #app code{
            color: #666;
            padding: 3px 5px;
            background: #f1f1f1;
            border-radius: 2px;
            font-size: 12px;
        }

        #app pre{
            /*width: 100%;*/
            /*display: block;*/
            color: #666;
            padding: 10px 10px;
            background: #f1f1f1;
            border-radius: 5px;
            font-size: 14px;
        }

        h1, h2, h3, h4, h5, h6{
          font-weight: 600;
          line-height: 1.25;
          margin-bottom: 0;
        }
        h1, h2{
          border-bottom: 1px solid #eaecef;
          padding-bottom: 12px;
        }
    </style>
</head>
<body>

<h2 style="text-align: center;margin-top: 50px;">JavaScript實現一個簡單的Markdown語法解析器</h2>
<div id="app">
    
    <div class="md-editor">
        <form>
            <textarea name="md-content" class="md-content" placeholder="在這裡使用markdown語法編寫"></textarea>
        </form>
    </div>
    <div class="md-html"><span class="placeholder">這裡會實時顯示markdown語法的解析結果</span></div>
</div>

<script type="text/javascript">

// 解析markdown語法為html
function markdownToHTML(markdown) {
  
  // 將Markdown文字分成多行
  const lines = markdown.split('\n');

  // 用於儲存解析後的HTML程式碼
  let result = '';

  // 遍歷每一行Markdown文字
  for (let i = 0; i < lines.length; i++) {

    const line = lines[i];
    
    // 處理標題
    if (line.startsWith('#')) {
      const level = line.match(/^#+/)[0].length;
      const title = line.substring(level).trim();
      result += `<h${level}>${title}</h${level}>`;
    }

    // 處理無序列表
    else if (line.startsWith('* ')) {
      const text = line.substring(2).trim();
      result += `<li>${text}</li>`;
    }

    // 處理有序列表
    else if (/^\d+\.\s/.test(line)) {
      const text = line.substring(line.indexOf('.') + 1).trim();
      result += `<li>${text}</li>`;
    }

    // 處理加粗
    else if (line.startsWith('**')) {
      result += line.replace(/\*\*(.*)\*\*/gm, '<strong>$1</strong>');
    }
    else if (line.startsWith('__')) {
      result += line.replace(/__(.*)__/gm, '<strong>$1</strong>');
    }

    // 處理斜體
    else if (line.startsWith('*')) {
      result += line.replace(/\*(.*)\*/gm, '<em>$1</em>');
    }
    else if (line.startsWith('_')) {
      result += line.replace(/_(.*)_/gm, '<em>$1</em>');
    }

    // 處理刪除線
    else if (line.startsWith('~~')) {
      result += line.replace(/~~(.*)~~/gm, '<del>$1</del>');
    }

    // 處理超連結
    else if (line.startsWith('[')) {
      result += line.replace(/\[(.*?)\]\((.*?)\)/gm, '<a href="$2">$1</a>');
    }

    // 處理圖片
    else if (line.startsWith('![')) {
      result += line.replace(/!\[(.*?)\]\((.*?)\)/gm, '<img src="$2" alt="$1" width="200" />');
    }
    
    // 處理行內程式碼
    else if (line.startsWith('`')) {
      result += escapeHtml(line).replace(/`(.*?)`/gm, '<code>$1</code>');
    }

    // 處理段落
    else {
      if(line.length == 0){
        result += `<br/>`;
      }else{
        result += `<p>${line}</p>`;
      }
    }

  }

  // 包裝HTML程式碼
  result = `<div>${result}</div>`;
  return result;
}

// html轉義
function escapeHtml(str) {
  return str.replace(/[&<>"']/g, function(match) {
    switch(match) {
      case '&':
        return '&amp;';
      case '<':
        return '&lt;';
      case '>':
        return '&gt;';
      case '"':
        return '&quot;';
      case '\'':
        return '&#39;';
    }
  });
}

// 實時解析markdown語法
$("#app .md-editor .md-content").bind("input propertychange",function(event){

    let md_content = $('#app .md-editor .md-content').val();

    if(md_content){

      $('#app .md-html').html(markdownToHTML(md_content));
    }else{

      $('#app .md-html').html('<span class="placeholder">這裡會實時顯示markdown語法的解析結果</span>');
    }
    
});


</script>
</body>
</html>

實現原理

實現起來非常簡單,就是透過正則替換預定的字元來實現HTML的輸出。

demo

image.png

作者

TANKING

相關文章