Преглед изворни кода

Added tsv2table tsv2table-h as a code parser

Sotaro KARASAWA пре 9 година
родитељ
комит
20a9e6c67f
3 измењених фајлова са 146 додато и 15 уклоњено
  1. 21 0
      resource/js/util/Crowi.js
  2. 40 15
      resource/js/util/CrowiRenderer.js
  3. 85 0
      resource/js/util/LangProcessor/Tsv2Table.js

+ 21 - 0
resource/js/util/Crowi.js

@@ -41,5 +41,26 @@ export default class Crowi {
     });
     });
   }
   }
 
 
+  static escape (html, encode) {
+    return html
+      .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&#39;');
+  }
+
+  static unescape(html) {
+    return html.replace(/&([#\w]+);/g, function(_, n) {
+      n = n.toLowerCase();
+      if (n === 'colon') return ':';
+      if (n.charAt(0) === '#') {
+        return n.charAt(1) === 'x'
+          ? String.fromCharCode(parseInt(n.substring(2), 16))
+          : String.fromCharCode(+n.substring(1));
+      }
+      return '';
+    });
+  }
 }
 }
 
 

+ 40 - 15
resource/js/util/CrowiRenderer.js

@@ -5,8 +5,11 @@ import MarkdownFixer from './PreProcessor/MarkdownFixer';
 import Linker from './PreProcessor/Linker';
 import Linker from './PreProcessor/Linker';
 import ImageExpander from './PreProcessor/ImageExpander';
 import ImageExpander from './PreProcessor/ImageExpander';
 
 
+import Tsv2Table from './LangProcessor/Tsv2Table';
+
 export default class CrowiRenderer {
 export default class CrowiRenderer {
 
 
+
   constructor(plugins) {
   constructor(plugins) {
     this.preProcessors = [
     this.preProcessors = [
       new MarkdownFixer(),
       new MarkdownFixer(),
@@ -16,6 +19,14 @@ export default class CrowiRenderer {
 
 
     this.postProcessors = [
     this.postProcessors = [
     ];
     ];
+
+    this.langProcessors = {
+      'tsv2table': new Tsv2Table(),
+      'tsv2table-h': new Tsv2Table({header: true}),
+    };
+
+    this.parseMarkdown = this.parseMarkdown.bind(this);
+    this.codeRenderer = this.codeRenderer.bind(this);
   }
   }
 
 
   preProcess(markdown) {
   preProcess(markdown) {
@@ -38,34 +49,48 @@ export default class CrowiRenderer {
     return html;
     return html;
   }
   }
 
 
+  codeRenderer(code, lang, escaped) {
+    let result = '', hl;
+
+    if (lang && this.langProcessors[lang]) {
+      return this.langProcessors[lang].process(code);
+    }
+
+    if (lang) {
+      try {
+        hl = hljs.highlight(lang, code);
+        result = hl.value;
+        escaped = true;
+      } catch (e) {
+        result = code;
+      }
+
+      result = (escape ? result : Crowi.escape(result, true));
+      return `<pre><code class="lang-${lang}">${result}\n</code></pre>\n`;
+    }
+
+    // no lang specified
+    return `<pre><code>${Crowi.escape(code, true)}\n</code></pre>`;
+
+  }
+
   parseMarkdown(markdown) {
   parseMarkdown(markdown) {
     let parsed = '';
     let parsed = '';
 
 
+    const markedRenderer = new marked.Renderer();
+    markedRenderer.code = this.codeRenderer;
+
     try {
     try {
       // TODO
       // TODO
       marked.setOptions({
       marked.setOptions({
         gfm: true,
         gfm: true,
-        highlight: function (code, lang) {
-          let result, hl;
-          if (lang) {
-            try {
-              hl = hljs.highlight(lang, code);
-              result = hl.value;
-            } catch (e) {
-              result = code;
-            }
-          } else {
-            result = code;
-          }
-          return result;
-        },
         tables: true,
         tables: true,
         breaks: true,
         breaks: true,
         pedantic: false,
         pedantic: false,
         sanitize: false,
         sanitize: false,
         smartLists: true,
         smartLists: true,
         smartypants: false,
         smartypants: false,
-        langPrefix: 'lang-'
+        renderer: markedRenderer,
       });
       });
 
 
       parsed = marked(markdown);
       parsed = marked(markdown);

+ 85 - 0
resource/js/util/LangProcessor/Tsv2Table.js

@@ -0,0 +1,85 @@
+import React from 'react';
+
+export default class Tsv2Table {
+
+  constructor(option) {
+    if (!option) {
+      option = {};
+    }
+    this.option = option;
+
+    this.option.header = this.option.header || false;
+  }
+  getCols(codeLines) {
+    let max = 0;
+
+    for (let i = 0; i < codeLines ; i++) {
+      if (max < codeLines.length) {
+        max = codeLines.length;
+      }
+    }
+
+    return max;
+  }
+
+  splitColums(line) {
+    // \t is replaced to '    ' by Lexer.lex(), so split by 4 spaces
+    return line.split(/\s{4}/g);
+  }
+
+  getTableHeader(codeLines, option) {
+    let headers = [];
+    let headLine = (codeLines[0] || '');
+
+    //console.log('head', headLine);
+    headers = this.splitColums(headLine).map(col => {
+      return `<th>${Crowi.escape(col)}</th>`;
+    });
+
+    if (headers.length < option.cols) {
+      headers.concat(new Array(option.cols - headers.length));
+    }
+
+    return `<tr>
+      ${headers.join('\n')}
+    </tr>`;
+  }
+
+  getTableBody(codeLines, option) {
+    let rows;
+
+    if (this.option.header) {
+      codeLines.shift();
+    }
+
+    rows = codeLines.map(row => {
+      const cols = this.splitColums(row).map(col => {
+        return `<td>${Crowi.escape(col)}</td>`;
+      }).join('');
+      return `<tr>${cols}</tr>`;
+    });
+
+    return rows.join('\n');
+  }
+
+  process(code) {
+    let option = {};
+    const codeLines = code.split(/\n|\r/);
+
+    option.cols = this.getCols(codeLines);
+
+    let header = '';
+    if (this.option.header) {
+      header = `<thead>
+        ${this.getTableHeader(codeLines, option)}
+      </thead>`;
+    }
+
+    return `<table>
+      ${header}
+      <tbody>
+        ${this.getTableBody(codeLines, option)}
+      </tbody>
+    </table>`;
+  }
+}