Просмотр исходного кода

feat(tables): add support for GFM tables

Github Flavored Markdown supports a specific table syntax. Table support was already available as an extension.
With this commit, the feature was moved to core, adding this feature to showdown through an option called "tables".

Related to #164
Estevão Soares dos Santos 10 лет назад
Родитель
Сommit
3a924e3c7e

+ 113 - 2
dist/showdown.js

@@ -16,7 +16,8 @@ var showdown = {},
       parseImgDimensions:        false,
       simplifiedAutoLink:        false,
       literalMidWordUnderscores: false,
-      strikethrough:             false
+      strikethrough:             false,
+      tables:                    false
     },
     globalOptions = JSON.parse(JSON.stringify(defaultOptions)); //clone default options out of laziness =P
 
@@ -456,6 +457,7 @@ showdown.Converter = function (converterOptions) {
        * @type {string[]}
        */
       parserOrder = [
+        'tables',
         'githubCodeBlocks',
         'hashHTMLBlocks',
         'stripLinkDefinitions',
@@ -907,7 +909,7 @@ showdown.subParser('blockGamut', function (text, options, globals) {
   var key = showdown.subParser('hashBlock')('<hr />', options, globals);
   text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
   text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
-  text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm, key);
+  text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key);
 
   text = showdown.subParser('lists')(text, options, globals);
   text = showdown.subParser('codeBlocks')(text, options, globals);
@@ -2004,6 +2006,115 @@ showdown.subParser('stripLinkDefinitions', function (text, options, globals) {
   return text;
 });
 
+showdown.subParser('tables', function (text, options, globals) {
+  'use strict';
+
+  var table = function () {
+
+    var tables = {},
+        style = 'text-align:left;',
+        filter;
+
+    tables.th = function (header) {
+      if (header.trim() === '') {
+        return '';
+      }
+      var id = header.trim().replace(/ /g, '_').toLowerCase();
+      return '<th id="' + id + '" style="' + style + '">' + header + '</th>';
+    };
+
+    tables.td = function (cell) {
+      var subText = showdown.subParser('blockGamut')(cell, options, globals);
+      return '<td style="' + style + '">' + subText + '</td>';
+    };
+
+    tables.ths = function () {
+      var out = '',
+        i = 0,
+        hs = [].slice.apply(arguments);
+      for (i; i < hs.length; i += 1) {
+        out += tables.th(hs[i]) + '\n';
+      }
+      return out;
+    };
+
+    tables.tds = function () {
+      var out = '', i = 0, ds = [].slice.apply(arguments);
+      for (i; i < ds.length; i += 1) {
+        out += tables.td(ds[i]) + '\n';
+      }
+      return out;
+    };
+
+    tables.thead = function () {
+      var out,
+        hs = [].slice.apply(arguments);
+      out = '<thead>\n';
+      out += '<tr>\n';
+      out += tables.ths.apply(this, hs);
+      out += '</tr>\n';
+      out += '</thead>\n';
+      return out;
+    };
+
+    tables.tr = function () {
+      var out,
+        cs = [].slice.apply(arguments);
+      out = '<tr>\n';
+      out += tables.tds.apply(this, cs);
+      out += '</tr>\n';
+      return out;
+    };
+
+    filter = function (text) {
+      var i = 0,
+        lines = text.split('\n'),
+        line,
+        hs,
+        out = [];
+      for (i; i < lines.length; i += 1) {
+        line = lines[i];
+        // looks like a table heading
+        if (line.trim().match(/^[|].*[|]$/)) {
+          line = line.trim();
+          var tbl = [];
+          tbl.push('<table>');
+          hs = line.substring(1, line.length - 1).split('|');
+          tbl.push(tables.thead.apply(this, hs));
+          line = lines[++i];
+          if (!line.trim().match(/^[|][-=|: ]+[|]$/)) {
+            // not a table rolling back
+            line = lines[--i];
+          } else {
+            line = lines[++i];
+            tbl.push('<tbody>');
+            while (line.trim().match(/^[|].*[|]$/)) {
+              line = line.trim();
+              tbl.push(tables.tr.apply(this, line.substring(1, line.length - 1).split('|')));
+              line = lines[++i];
+            }
+            tbl.push('</tbody>');
+            tbl.push('</table>');
+            // we are done with this table and we move along
+            out.push(tbl.join('\n'));
+            continue;
+          }
+        }
+        out.push(line);
+      }
+      return out.join('\n');
+    };
+    return {parse: filter};
+  };
+
+  if (options.tables) {
+    var tableParser = table();
+    return tableParser.parse(text);
+  } else {
+    return text;
+  }
+});
+
 /**
  * Swap back in all the special characters we've hidden.
  */

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/showdown.js.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/showdown.min.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/showdown.min.js.map


+ 1 - 0
src/converter.js

@@ -44,6 +44,7 @@ showdown.Converter = function (converterOptions) {
        * @type {string[]}
        */
       parserOrder = [
+        'tables',
         'githubCodeBlocks',
         'hashHTMLBlocks',
         'stripLinkDefinitions',

+ 2 - 1
src/showdown.js

@@ -14,7 +14,8 @@ var showdown = {},
       parseImgDimensions:        false,
       simplifiedAutoLink:        false,
       literalMidWordUnderscores: false,
-      strikethrough:             false
+      strikethrough:             false,
+      tables:                    false
     },
     globalOptions = JSON.parse(JSON.stringify(defaultOptions)); //clone default options out of laziness =P
 

+ 1 - 1
src/subParsers/blockGamut.js

@@ -11,7 +11,7 @@ showdown.subParser('blockGamut', function (text, options, globals) {
   var key = showdown.subParser('hashBlock')('<hr />', options, globals);
   text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
   text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
-  text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm, key);
+  text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key);
 
   text = showdown.subParser('lists')(text, options, globals);
   text = showdown.subParser('codeBlocks')(text, options, globals);

+ 108 - 0
src/subParsers/tables.js

@@ -0,0 +1,108 @@
+showdown.subParser('tables', function (text, options, globals) {
+  'use strict';
+
+  var table = function () {
+
+    var tables = {},
+        style = 'text-align:left;',
+        filter;
+
+    tables.th = function (header) {
+      if (header.trim() === '') {
+        return '';
+      }
+      var id = header.trim().replace(/ /g, '_').toLowerCase();
+      return '<th id="' + id + '" style="' + style + '">' + header + '</th>';
+    };
+
+    tables.td = function (cell) {
+      var subText = showdown.subParser('blockGamut')(cell, options, globals);
+      return '<td style="' + style + '">' + subText + '</td>';
+    };
+
+    tables.ths = function () {
+      var out = '',
+        i = 0,
+        hs = [].slice.apply(arguments);
+      for (i; i < hs.length; i += 1) {
+        out += tables.th(hs[i]) + '\n';
+      }
+      return out;
+    };
+
+    tables.tds = function () {
+      var out = '', i = 0, ds = [].slice.apply(arguments);
+      for (i; i < ds.length; i += 1) {
+        out += tables.td(ds[i]) + '\n';
+      }
+      return out;
+    };
+
+    tables.thead = function () {
+      var out,
+        hs = [].slice.apply(arguments);
+      out = '<thead>\n';
+      out += '<tr>\n';
+      out += tables.ths.apply(this, hs);
+      out += '</tr>\n';
+      out += '</thead>\n';
+      return out;
+    };
+
+    tables.tr = function () {
+      var out,
+        cs = [].slice.apply(arguments);
+      out = '<tr>\n';
+      out += tables.tds.apply(this, cs);
+      out += '</tr>\n';
+      return out;
+    };
+
+    filter = function (text) {
+      var i = 0,
+        lines = text.split('\n'),
+        line,
+        hs,
+        out = [];
+      for (i; i < lines.length; i += 1) {
+        line = lines[i];
+        // looks like a table heading
+        if (line.trim().match(/^[|].*[|]$/)) {
+          line = line.trim();
+          var tbl = [];
+          tbl.push('<table>');
+          hs = line.substring(1, line.length - 1).split('|');
+          tbl.push(tables.thead.apply(this, hs));
+          line = lines[++i];
+          if (!line.trim().match(/^[|][-=|: ]+[|]$/)) {
+            // not a table rolling back
+            line = lines[--i];
+          } else {
+            line = lines[++i];
+            tbl.push('<tbody>');
+            while (line.trim().match(/^[|].*[|]$/)) {
+              line = line.trim();
+              tbl.push(tables.tr.apply(this, line.substring(1, line.length - 1).split('|')));
+              line = lines[++i];
+            }
+            tbl.push('</tbody>');
+            tbl.push('</table>');
+            // we are done with this table and we move along
+            out.push(tbl.join('\n'));
+            continue;
+          }
+        }
+        out.push(line);
+      }
+      return out.join('\n');
+    };
+    return {parse: filter};
+  };
+
+  if (options.tables) {
+    var tableParser = table();
+    return tableParser.parse(text);
+  } else {
+    return text;
+  }
+});

+ 21 - 0
test/features/tables/basic-alignment.html

@@ -0,0 +1,21 @@
+<table>
+  <thead>
+    <tr>
+      <th id="first_header" style="text-align:left;"> First Header  </th>
+      <th id="second_header" style="text-align:left;"> Second Header </th>
+    </tr>
+  </thead>
+
+  <tbody>
+    <tr>
+      <td style="text-align:left;"><p>Row 1 Cell 1  </p></td>
+      <td style="text-align:left;"><p>Row 1 Cell 2  </p></td>
+    </tr>
+
+    <tr>
+      <td style="text-align:left;"><p>Row 2 Cell 1  </p></td>
+      <td style="text-align:left;"><p>Row 2 Cell 2  </p></td>
+    </tr>
+
+  </tbody>
+</table>

+ 4 - 0
test/features/tables/basic-alignment.md

@@ -0,0 +1,4 @@
+| First Header  | Second Header |
+| :------------ | :------------ |
+| Row 1 Cell 1  | Row 1 Cell 2  |
+| Row 2 Cell 1  | Row 2 Cell 2  |

+ 21 - 0
test/features/tables/basic.html

@@ -0,0 +1,21 @@
+<table>
+<thead>
+<tr>
+<th id="first_header" style="text-align:left;"> First Header  </th>
+<th id="second_header" style="text-align:left;"> Second Header </th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td style="text-align:left;"><p>Row 1 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 2  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 2 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 2  </p></td>
+</tr>
+
+</tbody>
+</table>

+ 4 - 0
test/features/tables/basic.md

@@ -0,0 +1,4 @@
+| First Header  | Second Header |
+| ------------- | ------------- |
+| Row 1 Cell 1  | Row 1 Cell 2  |
+| Row 2 Cell 1  | Row 2 Cell 2  |

+ 48 - 0
test/features/tables/large.html

@@ -0,0 +1,48 @@
+<table>
+<thead>
+<tr>
+<th id="first_header" style="text-align:left;"> First Header  </th>
+<th id="second_header" style="text-align:left;"> Second Header </th>
+<th id="third_header" style="text-align:left;"> Third Header  </th>
+<th id="fourth_header" style="text-align:left;"> Fourth Header </th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td style="text-align:left;"><p>Row 1 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 2  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 3  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 4  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 2 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 2  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 3  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 4  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 3 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 3 Cell 2  </p></td>
+<td style="text-align:left;"><p>Row 3 Cell 3  </p></td>
+<td style="text-align:left;"><p>Row 3 Cell 4  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 4 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 4 Cell 2  </p></td>
+<td style="text-align:left;"><p>Row 4 Cell 3  </p></td>
+<td style="text-align:left;"><p>Row 4 Cell 4  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 5 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 5 Cell 2  </p></td>
+<td style="text-align:left;"><p>Row 5 Cell 3  </p></td>
+<td style="text-align:left;"><p>Row 5 Cell 4  </p></td>
+</tr>
+
+</tbody>
+</table>

+ 7 - 0
test/features/tables/large.md

@@ -0,0 +1,7 @@
+| First Header  | Second Header | Third Header  | Fourth Header |
+| ------------- | ------------- | ------------  | ------------- |
+| Row 1 Cell 1  | Row 1 Cell 2  | Row 1 Cell 3  | Row 1 Cell 4  |
+| Row 2 Cell 1  | Row 2 Cell 2  | Row 2 Cell 3  | Row 2 Cell 4  |
+| Row 3 Cell 1  | Row 3 Cell 2  | Row 3 Cell 3  | Row 3 Cell 4  |
+| Row 4 Cell 1  | Row 4 Cell 2  | Row 4 Cell 3  | Row 4 Cell 4  |
+| Row 5 Cell 1  | Row 5 Cell 2  | Row 5 Cell 3  | Row 5 Cell 4  |

+ 43 - 0
test/features/tables/multiple-tables.html

@@ -0,0 +1,43 @@
+<h1 id="tabletest">Table Test</h1>
+
+<h2 id="section1">section 1</h2>
+
+<table>
+    <thead>
+    <tr>
+        <th id="header1" style="text-align:left;">header1    </th>
+        <th id="header2" style="text-align:left;">header2    </th>
+        <th id="header3" style="text-align:left;">header3</th>
+    </tr>
+    </thead>
+
+    <tbody>
+    <tr>
+        <td style="text-align:left;"><p>Value1     </p></td>
+        <td style="text-align:left;"><p>Value2     </p></td>
+        <td style="text-align:left;"><p>Value3   </p></td>
+    </tr>
+
+    </tbody>
+</table>
+
+<h2 id="section2">section 2</h2>
+
+<table>
+    <thead>
+    <tr>
+        <th id="headera" style="text-align:left;">headerA    </th>
+        <th id="headerb" style="text-align:left;">headerB    </th>
+        <th id="headerc" style="text-align:left;">headerC</th>
+    </tr>
+    </thead>
+
+    <tbody>
+    <tr>
+        <td style="text-align:left;"><p>ValueA     </p></td>
+        <td style="text-align:left;"><p>ValueB     </p></td>
+        <td style="text-align:left;"><p>ValueC   </p></td>
+    </tr>
+
+    </tbody>
+</table>

+ 17 - 0
test/features/tables/multiple-tables.md

@@ -0,0 +1,17 @@
+Table Test
+============
+
+section 1
+------------
+
+|header1    |header2    |header3|
+|-----------|-----------|---------|
+|Value1     |Value2     |Value3   |
+
+
+section 2
+-----------
+
+|headerA    |headerB    |headerC|
+|-----------|-----------|---------|
+|ValueA     |ValueB     |ValueC   |

+ 21 - 0
test/features/tables/with-equals.html

@@ -0,0 +1,21 @@
+<table>
+<thead>
+<tr>
+<th id="first_header" style="text-align:left;"> First Header  </th>
+<th id="second_header" style="text-align:left;"> Second Header </th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td style="text-align:left;"><p>Row 1 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 2  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 2 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 2  </p></td>
+</tr>
+
+</tbody>
+</table>

+ 4 - 0
test/features/tables/with-equals.md

@@ -0,0 +1,4 @@
+| First Header  | Second Header |
+| ============= | ============= |
+| Row 1 Cell 1  | Row 1 Cell 2  |
+| Row 2 Cell 1  | Row 2 Cell 2  |

+ 33 - 0
test/features/tables/with-surroundings.html

@@ -0,0 +1,33 @@
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent nisi est, 
+ullamcorper euismod iaculis sed, tristique at neque. Nullam metus risus, 
+malesuada vitae imperdiet ac, tincidunt eget lacus. Proin ullamcorper 
+vulputate dictum. Vestibulum consequat ultricies nibh, sed tempus nisl mattis a.</p> 
+
+<table>
+<thead>
+<tr>
+<th id="first_header" style="text-align:left;"> First Header  </th>
+<th id="second_header" style="text-align:left;"> Second Header </th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td style="text-align:left;"><p>Row 1 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 1 Cell 2  </p></td>
+</tr>
+
+<tr>
+<td style="text-align:left;"><p>Row 2 Cell 1  </p></td>
+<td style="text-align:left;"><p>Row 2 Cell 2  </p></td>
+</tr>
+
+</tbody>
+</table>
+
+<p>Phasellus ac porttitor quam. Integer cursus accumsan mauris nec interdum. 
+Etiam iaculis urna vitae risus facilisis faucibus eu quis risus. Sed aliquet 
+rutrum dictum. Vivamus pulvinar malesuada ultricies. Pellentesque in commodo 
+nibh. Maecenas justo erat, sodales vel bibendum a, dignissim in orci. Duis 
+blandit ornare mi non facilisis. Aliquam rutrum fringilla lacus in semper. 
+Sed vel pretium lorem.</p>

+ 16 - 0
test/features/tables/with-surroundings.md

@@ -0,0 +1,16 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent nisi est, 
+ullamcorper euismod iaculis sed, tristique at neque. Nullam metus risus, 
+malesuada vitae imperdiet ac, tincidunt eget lacus. Proin ullamcorper 
+vulputate dictum. Vestibulum consequat ultricies nibh, sed tempus nisl mattis a.
+
+| First Header  | Second Header |
+| ------------- | ------------- |
+| Row 1 Cell 1  | Row 1 Cell 2  |
+| Row 2 Cell 1  | Row 2 Cell 2  |
+
+Phasellus ac porttitor quam. Integer cursus accumsan mauris nec interdum. 
+Etiam iaculis urna vitae risus facilisis faucibus eu quis risus. Sed aliquet 
+rutrum dictum. Vivamus pulvinar malesuada ultricies. Pellentesque in commodo 
+nibh. Maecenas justo erat, sodales vel bibendum a, dignissim in orci. Duis 
+blandit ornare mi non facilisis. Aliquam rutrum fringilla lacus in semper. 
+Sed vel pretium lorem.

+ 11 - 0
test/features/tables/without-body.html

@@ -0,0 +1,11 @@
+<table>
+<thead>
+<tr>
+<th id="first_header" style="text-align:left;"> First Header  </th>
+<th id="second_header" style="text-align:left;"> Second Header </th>
+</tr>
+</thead>
+
+<tbody>
+</tbody>
+</table>

+ 2 - 0
test/features/tables/without-body.md

@@ -0,0 +1,2 @@
+| First Header  | Second Header |
+| ------------- | ------------- |

+ 1 - 0
test/features/tables/without-header-delimiter.html

@@ -0,0 +1 @@
+<p>| First Header  | Second Header |</p>

+ 1 - 0
test/features/tables/without-header-delimiter.md

@@ -0,0 +1 @@
+| First Header  | Second Header |

+ 2 - 1
test/node/showdown.js

@@ -25,7 +25,8 @@ describe('showdown.options', function () {
         parseImgDimensions:        false,
         simplifiedAutoLink:        false,
         literalMidWordUnderscores: false,
-        strikethrough:             false
+        strikethrough:             false,
+        tables:                    false
       };
       expect(showdown.getDefaultOptions()).to.be.eql(opts);
     });

+ 10 - 1
test/node/testsuite.features.js

@@ -4,7 +4,8 @@
 var showdown = require('../../dist/showdown.js'),
     bootstrap = require('../bootstrap.js'),
     assertion = bootstrap.assertion,
-    testsuite = bootstrap.getTestSuite('test/features/');
+    testsuite = bootstrap.getTestSuite('test/features/'),
+    tableSuite = bootstrap.getTestSuite('test/features/tables/');
 
 describe('makeHtml() features testsuite', function () {
   'use strict';
@@ -25,4 +26,12 @@ describe('makeHtml() features testsuite', function () {
     }
     it(testsuite[i].name, assertion(testsuite[i], converter));
   }
+
+  describe('table support', function () {
+    var converter = new showdown.Converter({tables: true});
+    for (var i = 0; i < tableSuite.length; ++i) {
+      it(tableSuite[i].name, assertion(tableSuite[i], converter));
+    }
+  });
+
 });

Некоторые файлы не были показаны из-за большого количества измененных файлов