Repository: Python-Markdown/markdown
Pull request #1097 (2021-01-18 14:21:03)

Update th/td to use style attribute
This allows better interoperation with CSS style sheets, as the align
object on the TH is skipped if the css uses 'text-align: inherit' and
the previous 'text-align' is used instead (or the default: left).

With the existing `align` attribute, standard CSS libraries, such as Bootstrap, can ignore the locally-attached `align` attribute by incorporating `text-align: inherit`. By setting the `text-align` CSS attribute in a `style` attribute attached to the th/td element, the local attribute overrides the CSS.  Without it, the lower-priority `align` attribute is ignored in these circumstances.

This is my first contribution to this project, so please let me know if there's something else you'd like. I think the commit messages, test updates, and change log updates are per spec.

waylan: Interesting. However, this extension is a strict clone of the PHP Markdown Extra implementation of tables. We expect our output to match that libraries’ output. This would change that. Are there any other implementations which use style attributes rather than align attributes for tables?

<sub>Sent with <a href="http://githawk.com">GitHawk</a></sub>
gaige: `align` and `valign` (along with attributes that directly manipulated display attributes) were deprecated in HTML4.  The current PHP implementation has 2 methods of doing alignment, the default is to still use the `align` attribute, but conditionally (with the definition of `table_align_class_tmpl`), it will insert a `class='$table_align_class_tmpl-{left|right|center}'`, which would allow for a somewhat more flexible implementation.  Unfortunately, that requires the introduction of an additional class, but if you'd prefer something like that, I could re-implement with that option. 

In that case, I'd be adding a config item to tables for `ALIGN_CLASS_TMPL` which would default to `None` and, if set, would add `class="<align_class>"` after replacing the `%%` in the string with the `left`, `right`, or `center` string.

That will stick most closely to the implementation in https://github.com/michelf/php-markdown.  I'm not entirely sure where to implement the test, although it appears test_syntax/extensions/test_tables might be a home for it.

Thoughts?
gaige: @waylan Any thoughts about my comments and/or @mitya57 comments?  Anything I can do to help move this fotward?
waylan: > current PHP implementation has 2 methods of doing alignment

That helps tremendously. Quite frankly I was surprised they were still using the `align` attribute. That said, technically, the original Markdown rules require the output to be XHTML. Of course, XHTML like syntax is a valid form of HTML5 so we mostly ignore the XHTML output requirement expect that the tags take on that form (`<hr />` as opposed to `<hr>`). No doubt this is where the current default of using the `align` attribute comes from.

We may still have some users who are expecting the older formats and are using the related doctypes in their documents. Would changing this cause their pages to no longer be valid? True XHTML (not XHTML served as HTML) is especially problematic in this regard and can cause a browser to crash hard if it is invalid (i don't recall if `style` attributes are valid or not). If there is any chance that some users are doing so (and I suspect there is), then we would need to have a configuration option to allow users to select there preferred option if we make any changes at all.

Another consideration are (X)HTML sanitizers. For example, GitHub's sanitizer strips all `style` attributes out of all user supplied content on their site(s). Therefore, they are still [using][1] `align` attributes in their output. True, our output is not being run through GitHub's sanitizer, but it certainly could be run through similarly configured sanitizers by some of our users.

Given the above, I think a configuration option for the extension makes the most sense. I think we could probably change the default and place a big warning in the release notes informing users that if they want/need `align` attributes rather than `style` attributes, then they need to set a custom option on the extension.

> I'm not entirely sure where to implement the test, although it appears test_syntax/extensions/test_tables might be a home for it.

Yes, that is correct, In fact, the existing tests should be moved there as part of this change. Our policy has been to move any related tests to the new system when the code related to those tests is updated.

[1]: https://github.github.com/gfm/#example-199
gaige: @waylan Changes have been made per discussion. Please let me know if there are any other changes.  I made a notation about the change, but perhaps there's either a better place to warn about it, or somewhere else you'd prefer, please let me know.

Also, checklinks is failing, but not not in files that I've been working in. Not sure what you want to do about that, but those are the only failing checks at this point.

waylan: This looks good. I will merge this once we are ready to start development on version 3.4.

Patch:
diff --git a/docs/change_log/index.md b/docs/change_log/index.md
index 09ace62..fc78087 100644
--- a/docs/change_log/index.md
+++ b/docs/change_log/index.md
@@ -3,6 +3,8 @@ title: Change Log
 Python-Markdown Change Log
 =========================
 
+Under development: version 3.4.0 ([Notes](release-3.4.md)).
+
 May 5, 2022: version 3.3.7 (a bug-fix release).
 
 * Disallow square brackets in reference link ids (#1209).
diff --git a/docs/change_log/release-3.4.md b/docs/change_log/release-3.4.md
new file mode 100644
index 0000000..0070da9
--- /dev/null
+++ b/docs/change_log/release-3.4.md
@@ -0,0 +1,44 @@
+title: Release Notes for v3.4
+
+# Python-Markdown 3.4 Release Notes
+
+Python-Markdown version 3.4 supports Python versions 3.6, 3.7, 3.8, 3.9 and PyPy3.
+
+## Backwards-incompatible changes
+
+### The `table` extension now uses a `style` attribute instead of `align` attribute for alignment.
+
+The [HTML4 spec][spec4] specifically
+deprecates the use of the `align` attribute and it does not appear at all in the 
+[HTML5 spec][spec5]. Therefore, by default, the [table] extension will now use the `style`
+attribute (setting just the `text-align` property) in `td` and `th` blocks.
+
+[spec4]: https://www.w3.org/TR/html4/present/graphics.html#h-15.1.2
+[spec5]: https://www.w3.org/TR/html53/tabular-data.html#attributes-common-to-td-and-th-elements
+
+The former behavior is available by setting the setting `use_align_attribute` configuration
+option to `True` when adding the extension.
+
+For example, to configure the old `align` behavior:
+
+```python
+from markdown.extensions.tables import TableExtension
+
+markdown.markdown(src, extensions=[TableExtension(use_align_attribute=True)])
+```
+
+In addition, tests were moved to the modern test environment.
+
+## New features
+
+The following new features have been included in the 3.3 release:
+
+* Use `style` attribute in tables for alignment instead of `align` for better CSS
+  inter-operation. The old behavior is available by setting `use_align_attribute=True` when
+  adding the extension.
+
+## Bug fixes
+
+The following bug fixes are included in the 3.4 release:
+
+
diff --git a/docs/extensions/tables.md b/docs/extensions/tables.md
index 30b7636..aaffc09 100644
--- a/docs/extensions/tables.md
+++ b/docs/extensions/tables.md
@@ -58,10 +58,30 @@ Usage
 See [Extensions](index.md) for general extension usage. Use `tables` as the
 name of the extension.
 
-This extension does not accept any special configuration options.
+See the [Library Reference](../reference.md#extensions) for information about
+configuring extensions.
+
+The following options are provided to change the default behavior:
+
+* **`use_align_attribute`**: Set to `True` to use `align` instead of an appropriate `style` attribute
+
+    Default: `'False'`
+
 
 A trivial example:
 
 ```python
 markdown.markdown(some_text, extensions=['tables'])
 ```
+
+### Examples
+
+For an example, let us suppose that alignment should be controlled by the legacy `align`
+attribute.
+
+```pycon
+>>> from markdown.extensions.tables import TableExtension
+>>> html = markdown.markdown(text,
+...                 extensions=[TableExtension(use_align_attribute=True)]
+... )
+```
diff --git a/markdown/extensions/tables.py b/markdown/extensions/tables.py
index 0a9d084..c8b1024 100644
--- a/markdown/extensions/tables.py
+++ b/markdown/extensions/tables.py
@@ -30,9 +30,11 @@ class TableProcessor(BlockProcessor):
     RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))')
     RE_END_BORDER = re.compile(r'(?<!\\)(?:\\\\)*\|$')
 
-    def __init__(self, parser):
+    def __init__(self, parser, config):
         self.border = False
         self.separator = ''
+        self.config = config
+
         super().__init__(parser)
 
     def test(self, parent, block):
@@ -126,7 +128,10 @@ class TableProcessor(BlockProcessor):
             except IndexError:  # pragma: no cover
                 c.text = ""
             if a:
-                c.set('align', a)
+                if self.config['use_align_attribute']:
+                    c.set('align', a)
+                else:
+                    c.set('style', f'text-align: {a};')
 
     def _split_row(self, row):
         """ split a row of text into list of cells. """
@@ -212,11 +217,19 @@ class TableProcessor(BlockProcessor):
 class TableExtension(Extension):
     """ Add tables to Markdown. """
 
+    def __init__(self, **kwargs):
+        self.config = {
+            'use_align_attribute': [False, 'True to use align attribute instead of style.'],
+        }
+
+        super().__init__(**kwargs)
+
     def extendMarkdown(self, md):
         """ Add an instance of TableProcessor to BlockParser. """
         if '|' not in md.ESCAPED_CHARS:
             md.ESCAPED_CHARS.append('|')
-        md.parser.blockprocessors.register(TableProcessor(md.parser), 'table', 75)
+        processor = TableProcessor(md.parser, self.getConfigs())
+        md.parser.blockprocessors.register(processor, 'table', 75)
 
 
 def makeExtension(**kwargs):  # pragma: no cover


Test patch:
diff --git a/tests/extensions/extra/tables.html b/tests/extensions/extra/tables.html
deleted file mode 100644
index 25dee48..0000000
--- a/tests/extensions/extra/tables.html
+++ /dev/null
@@ -1,466 +0,0 @@
-<h2>Table Tests</h2>
-<table>
-<thead>
-<tr>
-<th>First Header</th>
-<th>Second Header</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>First Header</th>
-<th>Second Header</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th align="left">Item</th>
-<th align="right">Value</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td align="left">Computer</td>
-<td align="right">$1600</td>
-</tr>
-<tr>
-<td align="left">Phone</td>
-<td align="right">$12</td>
-</tr>
-<tr>
-<td align="left">Pipe</td>
-<td align="right">$1</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Function name</th>
-<th>Description</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td><code>help()</code></td>
-<td>Display the help window.</td>
-</tr>
-<tr>
-<td><code>destroy()</code></td>
-<td><strong>Destroy your computer!</strong></td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th align="left">foo</th>
-<th align="center">bar</th>
-<th align="right">baz</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td align="left"></td>
-<td align="center">Q</td>
-<td align="right"></td>
-</tr>
-<tr>
-<td align="left">W</td>
-<td align="center"></td>
-<td align="right">W</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>foo</th>
-<th>bar</th>
-<th>baz</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td></td>
-<td>Q</td>
-<td></td>
-</tr>
-<tr>
-<td>W</td>
-<td></td>
-<td>W</td>
-</tr>
-</tbody>
-</table>
-<p>Three spaces in front of a table:</p>
-<table>
-<thead>
-<tr>
-<th>First Header</th>
-<th>Second Header</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>First Header</th>
-<th>Second Header</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-<tr>
-<td>Content Cell</td>
-<td>Content Cell</td>
-</tr>
-</tbody>
-</table>
-<p>Four spaces is a code block:</p>
-<pre><code>First Header | Second Header
------------- | -------------
-Content Cell | Content Cell
-Content Cell | Content Cell
-</code></pre>
-<table>
-<thead>
-<tr>
-<th>First Header</th>
-<th>Second Header</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td></td>
-<td></td>
-</tr>
-</tbody>
-</table>
-<p>More inline code block tests</p>
-<table>
-<thead>
-<tr>
-<th>Column 1</th>
-<th>Column 2</th>
-<th>Column 3</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>word 1</td>
-<td>word 2</td>
-<td>word 3</td>
-</tr>
-<tr>
-<td>word 1</td>
-<td><code>word 2</code></td>
-<td>word 3</td>
-</tr>
-<tr>
-<td>word 1</td>
-<td>`word 2</td>
-<td>word 3</td>
-</tr>
-<tr>
-<td>word 1</td>
-<td>`word 2</td>
-<td>word 3</td>
-</tr>
-<tr>
-<td>word 1</td>
-<td><code>word |2</code></td>
-<td>word 3</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some | code</code></td>
-<td>more words</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some | code</code></td>
-<td>more words</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some | code</code></td>
-<td>more words</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some ` | ` code</code></td>
-<td>more words</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some ` | ` code</code></td>
-<td>more words</td>
-</tr>
-<tr>
-<td>words</td>
-<td><code>some ` | ` code</code></td>
-<td>more words</td>
-</tr>
-</tbody>
-</table>
-<p>A test for issue #440:</p>
-<table>
-<thead>
-<tr>
-<th>foo</th>
-<th>bar</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>foo</td>
-<td>(<code>bar</code>) and <code>baz</code>.</td>
-</tr>
-</tbody>
-</table>
-<p>Lists are not tables</p>
-<ul>
-<li>this | should | not</li>
-<li>be | a | table</li>
-</ul>
-<p>Add tests for issue #449</p>
-<table>
-<thead>
-<tr>
-<th>Odd backticks</th>
-<th>Even backticks</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td><code>[!\"\#$%&amp;'()*+,\-./:;&lt;=&gt;?@\[\\\]^_`{|}~]</code></td>
-<td><code>[!\"\#$%&amp;'()*+,\-./:;&lt;=&gt;?@\[\\\]^`_`{|}~]</code></td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Escapes</th>
-<th>More Escapes</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td><code>`\</code></td>
-<td><code>\</code></td>
-</tr>
-</tbody>
-</table>
-<p>Only the first backtick can be escaped</p>
-<table>
-<thead>
-<tr>
-<th>Escaped</th>
-<th>Bacticks</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>`<code>\</code></td>
-<td>``</td>
-</tr>
-</tbody>
-</table>
-<p>Test escaped pipes</p>
-<table>
-<thead>
-<tr>
-<th>Column 1</th>
-<th>Column 2</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td><code>|</code> |</td>
-<td>Pipes are okay in code and escaped. |</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Column 1</th>
-<th>Column 2</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>row1</td>
-<td>row1    |</td>
-</tr>
-<tr>
-<td>row2</td>
-<td>row2</td>
-</tr>
-</tbody>
-</table>
-<p>Test header escapes</p>
-<table>
-<thead>
-<tr>
-<th><code>`\</code> |</th>
-<th><code>\</code> |</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>row1</td>
-<td>row1</td>
-</tr>
-<tr>
-<td>row2</td>
-<td>row2</td>
-</tr>
-</tbody>
-</table>
-<p>Escaped pipes in format row should not be a table</p>
-<p>| Column1   | Column2 |
-| ------- || ------- |
-| row1      | row1    |
-| row2      | row2    |</p>
-<p>Test escaped code in Table</p>
-<table>
-<thead>
-<tr>
-<th>Should not be code</th>
-<th>Should be code</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>`Not code`</td>
-<td>\<code>code</code></td>
-</tr>
-<tr>
-<td>\`Not code\`</td>
-<td>\\<code>code</code></td>
-</tr>
-</tbody>
-</table>
-<p>Single column tables</p>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td></td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td></td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td></td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>row</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>row</td>
-</tr>
-</tbody>
-</table>
-<table>
-<thead>
-<tr>
-<th>Is a Table</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>row</td>
-</tr>
-</tbody>
-</table>
-<h2>| Is not a Table</h2>
-<p>| row</p>
-<h2>Is not a Table |</h2>
-<p>row            |</p>
-<p>| Is not a Table
-| --------------
-row</p>
-<p>Is not a Table |
--------------- |
-row</p>
\ No newline at end of file
diff --git a/tests/extensions/extra/tables.txt b/tests/extensions/extra/tables.txt
deleted file mode 100644
index 2dc4967..0000000
--- a/tests/extensions/extra/tables.txt
+++ /dev/null
@@ -1,169 +0,0 @@
-Table Tests
------------
-
-First Header  | Second Header
-------------- | -------------
-Content Cell  | Content Cell
-Content Cell  | Content Cell
-
-| First Header  | Second Header |
-| ------------- | ------------- |
-| Content Cell  | Content Cell  |
-| Content Cell  | Content Cell  |
-
-| Item      | Value |
-| :-------- | -----:|
-| Computer  | $1600 |
-| Phone     |   $12 |
-| Pipe      |    $1 |
-
-| Function name | Description                    |
-| ------------- | ------------------------------ |
-| `help()`      | Display the help window.       |
-| `destroy()`   | **Destroy your computer!**     |
-
-|foo|bar|baz|
-|:--|:-:|--:|
-|   | Q |   |
-|W  |   |  W|
-
-foo|bar|baz
----|---|---
-   | Q |
- W |   | W
-
-Three spaces in front of a table:
-
-   First Header | Second Header
-   ------------ | -------------
-   Content Cell | Content Cell
-   Content Cell | Content Cell
-
-   | First Header | Second Header |
-   | ------------ | ------------- |
-   | Content Cell | Content Cell  |
-   | Content Cell | Content Cell  |
-
-Four spaces is a code block:
-
-    First Header | Second Header
-    ------------ | -------------
-    Content Cell | Content Cell
-    Content Cell | Content Cell
-
-| First Header | Second Header |
-| ------------ | ------------- |
-
-More inline code block tests
-
-Column 1 | Column 2 | Column 3
----------|----------|---------
-word 1   | word 2   | word 3
-word 1   | `word 2` | word 3
-word 1   | \`word 2 | word 3
-word 1   | `word 2 | word 3
-word 1   | `word |2` | word 3
-words    |`` some | code `` | more words
-words    |``` some | code ``` | more words
-words    |```` some | code ```` | more words
-words    |`` some ` | ` code `` | more words
-words    |``` some ` | ` code ``` | more words
-words    |```` some ` | ` code ```` | more words
-
-A test for issue #440:
-
-foo | bar
---- | ---
-foo | (`bar`) and `baz`.
-
-Lists are not tables
-
- - this | should | not
- - be | a | table
-
-Add tests for issue #449
-
-Odd backticks | Even backticks
------------- | -------------
-``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]`` | ``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^`_`{|}~]``
-
-Escapes | More Escapes
-------- | ------
-`` `\`` | `\`
-
-Only the first backtick can be escaped
-
-Escaped | Bacticks
-------- | ------
-\`` \`  | \`\`
-
-Test escaped pipes
-
-Column 1 | Column 2
--------- | --------
-`|` \|   | Pipes are okay in code and escaped. \|
-
-| Column 1 | Column 2 |
-| -------- | -------- |
-| row1     | row1    \|
-| row2     | row2     |
-
-Test header escapes
-
-| `` `\`` \| | `\` \|
-| ---------- | ---- |
-| row1       | row1 |
-| row2       | row2 |
-
-Escaped pipes in format row should not be a table
-
-| Column1   | Column2 |
-| ------- \|| ------- |
-| row1      | row1    |
-| row2      | row2    |
-
-Test escaped code in Table
-
-Should not be code | Should be code
------------------- | --------------
-\`Not code\`       | \\`code`
-\\\`Not code\\\`   | \\\\`code`
-
-Single column tables
-
-| Is a Table |
-| ---------- |
-
-| Is a Table
-| ----------
-
-Is a Table |
----------- |
-
-| Is a Table |
-| ---------- |
-| row        |
-
-| Is a Table
-| ----------
-| row
-
-Is a Table |
----------- |
-row        |
-
-| Is not a Table
---------------
-| row
-
-Is not a Table |
---------------
-row            |
-
-| Is not a Table
-| --------------
-row
-
-Is not a Table |
--------------- |
-row
diff --git a/tests/test_legacy.py b/tests/test_legacy.py
index 363161a..62bc075 100644
--- a/tests/test_legacy.py
+++ b/tests/test_legacy.py
@@ -182,8 +182,6 @@ class TestExtensionsExtra(LegacyTestCase):
 
     footnotes = Kwargs(extensions=['footnotes'])
 
-    tables = Kwargs(extensions=['tables'])
-
     extra_config = Kwargs(
         extensions=['extra'],
         extension_configs={
diff --git a/tests/test_syntax/extensions/test_tables.py b/tests/test_syntax/extensions/test_tables.py
index b01ea0c..cd3fbe4 100644
--- a/tests/test_syntax/extensions/test_tables.py
+++ b/tests/test_syntax/extensions/test_tables.py
@@ -20,6 +20,7 @@ License: BSD (see LICENSE.md for details).
 """
 
 from markdown.test_tools import TestCase
+from markdown.extensions.tables import TableExtension
 
 
 class TestTableBlocks(TestCase):
@@ -60,3 +61,862 @@ Content Cell  |  
             ),
             extensions=['tables']
         )
+
+    def test_no_sides(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                First Header  | Second Header
+                ------------- | -------------
+                Content Cell  | Content Cell
+                Content Cell  | Content Cell
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th>First Header</th>
+                <th>Second Header</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_both_sides(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                | First Header  | Second Header |
+                | ------------- | ------------- |
+                | Content Cell  | Content Cell  |
+                | Content Cell  | Content Cell  |
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th>First Header</th>
+                <th>Second Header</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_align_columns(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                | Item      | Value |
+                | :-------- | -----:|
+                | Computer  | $1600 |
+                | Phone     |   $12 |
+                | Pipe      |    $1 |
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th style="text-align: left;">Item</th>
+                <th style="text-align: right;">Value</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td style="text-align: left;">Computer</td>
+                <td style="text-align: right;">$1600</td>
+                </tr>
+                <tr>
+                <td style="text-align: left;">Phone</td>
+                <td style="text-align: right;">$12</td>
+                </tr>
+                <tr>
+                <td style="text-align: left;">Pipe</td>
+                <td style="text-align: right;">$1</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_styles_in_tables(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                | Function name | Description                    |
+                | ------------- | ------------------------------ |
+                | `help()`      | Display the help window.       |
+                | `destroy()`   | **Destroy your computer!**     |
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th>Function name</th>
+                <th>Description</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td><code>help()</code></td>
+                <td>Display the help window.</td>
+                </tr>
+                <tr>
+                <td><code>destroy()</code></td>
+                <td><strong>Destroy your computer!</strong></td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_align_three(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                |foo|bar|baz|
+                |:--|:-:|--:|
+                |   | Q |   |
+                |W  |   |  W|
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th style="text-align: left;">foo</th>
+                <th style="text-align: center;">bar</th>
+                <th style="text-align: right;">baz</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td style="text-align: left;"></td>
+                <td style="text-align: center;">Q</td>
+                <td style="text-align: right;"></td>
+                </tr>
+                <tr>
+                <td style="text-align: left;">W</td>
+                <td style="text-align: center;"></td>
+                <td style="text-align: right;">W</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_three_columns(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                foo|bar|baz
+                ---|---|---
+                   | Q |
+                 W |   | W
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th>foo</th>
+                <th>bar</th>
+                <th>baz</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td></td>
+                <td>Q</td>
+                <td></td>
+                </tr>
+                <tr>
+                <td>W</td>
+                <td></td>
+                <td>W</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_three_spaces_prefix(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Three spaces in front of a table:
+
+                   First Header | Second Header
+                   ------------ | -------------
+                   Content Cell | Content Cell
+                   Content Cell | Content Cell
+
+                   | First Header | Second Header |
+                   | ------------ | ------------- |
+                   | Content Cell | Content Cell  |
+                   | Content Cell | Content Cell  |
+                """),
+            self.dedent(
+                """
+                <p>Three spaces in front of a table:</p>
+                <table>
+                <thead>
+                <tr>
+                <th>First Header</th>
+                <th>Second Header</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>First Header</th>
+                <th>Second Header</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                <tr>
+                <td>Content Cell</td>
+                <td>Content Cell</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_code_block_table(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Four spaces is a code block:
+
+                    First Header | Second Header
+                    ------------ | -------------
+                    Content Cell | Content Cell
+                    Content Cell | Content Cell
+
+                | First Header | Second Header |
+                | ------------ | ------------- |
+                """),
+            self.dedent(
+                """
+                <p>Four spaces is a code block:</p>
+                <pre><code>First Header | Second Header
+                ------------ | -------------
+                Content Cell | Content Cell
+                Content Cell | Content Cell
+                </code></pre>
+                <table>
+                <thead>
+                <tr>
+                <th>First Header</th>
+                <th>Second Header</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td></td>
+                <td></td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_inline_code_blocks(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                More inline code block tests
+
+                Column 1 | Column 2 | Column 3
+                ---------|----------|---------
+                word 1   | word 2   | word 3
+                word 1   | `word 2` | word 3
+                word 1   | \\`word 2 | word 3
+                word 1   | `word 2 | word 3
+                word 1   | `word |2` | word 3
+                words    |`` some | code `` | more words
+                words    |``` some | code ``` | more words
+                words    |```` some | code ```` | more words
+                words    |`` some ` | ` code `` | more words
+                words    |``` some ` | ` code ``` | more words
+                words    |```` some ` | ` code ```` | more words
+                """),
+            self.dedent(
+                """
+                <p>More inline code block tests</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Column 1</th>
+                <th>Column 2</th>
+                <th>Column 3</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>word 1</td>
+                <td>word 2</td>
+                <td>word 3</td>
+                </tr>
+                <tr>
+                <td>word 1</td>
+                <td><code>word 2</code></td>
+                <td>word 3</td>
+                </tr>
+                <tr>
+                <td>word 1</td>
+                <td>`word 2</td>
+                <td>word 3</td>
+                </tr>
+                <tr>
+                <td>word 1</td>
+                <td>`word 2</td>
+                <td>word 3</td>
+                </tr>
+                <tr>
+                <td>word 1</td>
+                <td><code>word |2</code></td>
+                <td>word 3</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some | code</code></td>
+                <td>more words</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some | code</code></td>
+                <td>more words</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some | code</code></td>
+                <td>more words</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some ` | ` code</code></td>
+                <td>more words</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some ` | ` code</code></td>
+                <td>more words</td>
+                </tr>
+                <tr>
+                <td>words</td>
+                <td><code>some ` | ` code</code></td>
+                <td>more words</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_issue_440(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                A test for issue #440:
+
+                foo | bar
+                --- | ---
+                foo | (`bar`) and `baz`.
+                """),
+            self.dedent(
+                """
+                <p>A test for issue #440:</p>
+                <table>
+                <thead>
+                <tr>
+                <th>foo</th>
+                <th>bar</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>foo</td>
+                <td>(<code>bar</code>) and <code>baz</code>.</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_lists_not_tables(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Lists are not tables
+
+                 - this | should | not
+                 - be | a | table
+                """),
+            self.dedent(
+                """
+                <p>Lists are not tables</p>
+                <ul>
+                <li>this | should | not</li>
+                <li>be | a | table</li>
+                </ul>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_issue_449(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                r"""
+                Add tests for issue #449
+
+                Odd backticks | Even backticks
+                ------------ | -------------
+                ``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]`` | ``[!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^`_`{|}~]``
+
+                Escapes | More Escapes
+                ------- | ------
+                `` `\`` | `\`
+
+                Only the first backtick can be escaped
+
+                Escaped | Bacticks
+                ------- | ------
+                \`` \`  | \`\`
+
+                Test escaped pipes
+
+                Column 1 | Column 2
+                -------- | --------
+                `|` \|   | Pipes are okay in code and escaped. \|
+
+                | Column 1 | Column 2 |
+                | -------- | -------- |
+                | row1     | row1    \|
+                | row2     | row2     |
+
+                Test header escapes
+
+                | `` `\`` \| | `\` \|
+                | ---------- | ---- |
+                | row1       | row1 |
+                | row2       | row2 |
+
+                Escaped pipes in format row should not be a table
+
+                | Column1   | Column2 |
+                | ------- \|| ------- |
+                | row1      | row1    |
+                | row2      | row2    |
+
+                Test escaped code in Table
+
+                Should not be code | Should be code
+                ------------------ | --------------
+                \`Not code\`       | \\`code`
+                \\\`Not code\\\`   | \\\\`code`
+                """),
+            self.dedent(
+                """
+                <p>Add tests for issue #449</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Odd backticks</th>
+                <th>Even backticks</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td><code>[!\\"\\#$%&amp;'()*+,\\-./:;&lt;=&gt;?@\\[\\\\\\]^_`{|}~]</code></td>
+                <td><code>[!\\"\\#$%&amp;'()*+,\\-./:;&lt;=&gt;?@\\[\\\\\\]^`_`{|}~]</code></td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Escapes</th>
+                <th>More Escapes</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td><code>`\\</code></td>
+                <td><code>\\</code></td>
+                </tr>
+                </tbody>
+                </table>
+                <p>Only the first backtick can be escaped</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Escaped</th>
+                <th>Bacticks</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>`<code>\\</code></td>
+                <td>``</td>
+                </tr>
+                </tbody>
+                </table>
+                <p>Test escaped pipes</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Column 1</th>
+                <th>Column 2</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td><code>|</code> |</td>
+                <td>Pipes are okay in code and escaped. |</td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Column 1</th>
+                <th>Column 2</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>row1</td>
+                <td>row1    |</td>
+                </tr>
+                <tr>
+                <td>row2</td>
+                <td>row2</td>
+                </tr>
+                </tbody>
+                </table>
+                <p>Test header escapes</p>
+                <table>
+                <thead>
+                <tr>
+                <th><code>`\\</code> |</th>
+                <th><code>\\</code> |</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>row1</td>
+                <td>row1</td>
+                </tr>
+                <tr>
+                <td>row2</td>
+                <td>row2</td>
+                </tr>
+                </tbody>
+                </table>
+                <p>Escaped pipes in format row should not be a table</p>
+                <p>| Column1   | Column2 |
+                | ------- || ------- |
+                | row1      | row1    |
+                | row2      | row2    |</p>
+                <p>Test escaped code in Table</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Should not be code</th>
+                <th>Should be code</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>`Not code`</td>
+                <td>\\<code>code</code></td>
+                </tr>
+                <tr>
+                <td>\\`Not code\\`</td>
+                <td>\\\\<code>code</code></td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_single_column_tables(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                Single column tables
+
+                | Is a Table |
+                | ---------- |
+
+                | Is a Table
+                | ----------
+
+                Is a Table |
+                ---------- |
+
+                | Is a Table |
+                | ---------- |
+                | row        |
+
+                | Is a Table
+                | ----------
+                | row
+
+                Is a Table |
+                ---------- |
+                row        |
+
+                | Is not a Table
+                --------------
+                | row
+
+                Is not a Table |
+                --------------
+                row            |
+
+                | Is not a Table
+                | --------------
+                row
+
+                Is not a Table |
+                -------------- |
+                row
+                """),
+            self.dedent(
+                """
+                <p>Single column tables</p>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td></td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td></td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td></td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>row</td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>row</td>
+                </tr>
+                </tbody>
+                </table>
+                <table>
+                <thead>
+                <tr>
+                <th>Is a Table</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td>row</td>
+                </tr>
+                </tbody>
+                </table>
+                <h2>| Is not a Table</h2>
+                <p>| row</p>
+                <h2>Is not a Table |</h2>
+                <p>row            |</p>
+                <p>| Is not a Table
+                | --------------
+                row</p>
+                <p>Is not a Table |
+                -------------- |
+                row</p>
+                """
+            ),
+            extensions=['tables']
+        )
+
+    def test_align_columns_legacy(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                | Item      | Value |
+                | :-------- | -----:|
+                | Computer  | $1600 |
+                | Phone     |   $12 |
+                | Pipe      |    $1 |
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th align="left">Item</th>
+                <th align="right">Value</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td align="left">Computer</td>
+                <td align="right">$1600</td>
+                </tr>
+                <tr>
+                <td align="left">Phone</td>
+                <td align="right">$12</td>
+                </tr>
+                <tr>
+                <td align="left">Pipe</td>
+                <td align="right">$1</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=[TableExtension(use_align_attribute=True)]
+        )
+
+    def test_align_three_legacy(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                |foo|bar|baz|
+                |:--|:-:|--:|
+                |   | Q |   |
+                |W  |   |  W|
+                """
+            ),
+            self.dedent(
+                """
+                <table>
+                <thead>
+                <tr>
+                <th align="left">foo</th>
+                <th align="center">bar</th>
+                <th align="right">baz</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr>
+                <td align="left"></td>
+                <td align="center">Q</td>
+                <td align="right"></td>
+                </tr>
+                <tr>
+                <td align="left">W</td>
+                <td align="center"></td>
+                <td align="right">W</td>
+                </tr>
+                </tbody>
+                </table>
+                """
+            ),
+            extensions=[TableExtension(use_align_attribute=True)]
+        )