Skip to content

Commit d700d0a

Browse files
author
Waylan Limberg
committed
Merge pull request #260 from ryneeverett/master
Issue #52 Patch -- Markdown Inside HTML Blocks
2 parents 191d88b + daa2d46 commit d700d0a

File tree

6 files changed

+330
-57
lines changed

6 files changed

+330
-57
lines changed

docs/extensions/extra.txt

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,99 @@ therefore, not part of Python-Markdown Extra. If you really would
4141
like Extra to include additional extensions, we suggest creating
4242
your own clone of Extra under a different name
4343
(see the [Extension API](api.html)).
44+
45+
Markdown Inside HTML Blocks
46+
---------------------------
47+
48+
Unlike the other Extra features, this feature is build into the markdown core and is turned on when `extra` is enabled.
49+
50+
The content of any block-level element can be Markdown-formatted simply by adding a `markdown` attribute to the opening tag. The markdown attribute will be stripped from the output, but all other attributes will be preserved.
51+
52+
If the markdown value is set to `1` (recommended) or any value other than `span` or `block`, the default behavior will be executed: `p`,`h[1-6]`,`li`,`dd`,`dt`,`td`,`th`,`legend`, and `address` elements skip block parsing while others do not. If the default is overrident by a value of `span`, *block parsing will be skipped* regardless of tag. If the default is overriden by a value of `block`, *block parsing will occur* regardless of tag.
53+
54+
*An opening tag with the markdown attribute must start immediately on a line following a blank line.*
55+
56+
#### Simple Example:
57+
```
58+
This is *true* markdown text.
59+
60+
<div markdown="1">
61+
This is *true* markdown text.
62+
</div>
63+
```
64+
#### Result:
65+
```
66+
<p>This is <em>true</em> markdown text.</p>
67+
<div>
68+
<p>This is <em>true</em> markdown text.</p>
69+
</div>
70+
```
71+
72+
### Nested Markdown Inside HTML BLocks
73+
Nested elements are more sensitive and must be used cautiously. Violation of the following will lead to unexpected behavior or unhandled exceptions.
74+
* Only block mode elements may have further elements nested within them.
75+
* The closing tag of inner elements must be followed by a blank line.
76+
* More than one level of nesting is not supported (i.e., elements nested within elements nested within elements). This feature is not an alternative to templating.
77+
78+
#### Complex Example:
79+
```
80+
<div markdown="1" name="Example">
81+
82+
The text of the `Example` element.
83+
84+
<div markdown="1" name="DefaultBlockMode">
85+
This text gets wrapped in `p` tags.
86+
</div>
87+
88+
The tail of the `DefaultBlockMode` subelement.
89+
90+
<p markdown="1" name="DefaultSpanMode">
91+
This text *is not* wrapped in additional `p` tags.
92+
</p>
93+
94+
The tail of the `DefaultSpanMode` subelement.
95+
96+
<div markdown="span" name="SpanModeOverride">
97+
This `div` block is not wrapped in paragraph tags.
98+
Note: Subelements are not required to have tail text.
99+
</div>
100+
101+
<p markdown="block" name="BlockModeOverride">
102+
This `p` block *is* foolishly wrapped in further paragraph tags.
103+
</p>
104+
105+
The tail of the `BlockModeOverride` subelement.
106+
107+
<div name="RawHtml">
108+
Raw html blocks may also be nested.
109+
</div>
110+
111+
</div>
112+
113+
This text is after the markdown in html.
114+
```
115+
#### Result:
116+
```
117+
<div name="Example">
118+
<p>The text of the <code>Example</code> element.</p>
119+
<div name="DefaultBlockMode">
120+
<p>This text gets wrapped in <code>p</code> tags.</p>
121+
</div>
122+
<p>The tail of the <code>DefaultBlockMode</code> subelement.</p>
123+
<p name="DefaultSpanMode">
124+
This text <em>is not</em> wrapped in additional <code>p</code> tags.</p>
125+
<p>The tail of the <code>DefaultSpanMode</code> subelement.</p>
126+
<div name="SpanModeOverride">
127+
This <code>div</code> block is not wrapped in paragraph tags.
128+
Note: Subelements are not required to have tail text.</div>
129+
<p name="BlockModeOverride">
130+
<p>This <code>p</code> block <em>is</em> foolishly wrapped in further paragraph tags.</p>
131+
</p>
132+
<p>The tail of the <code>BlockModeOverride</code> subelement.</p>
133+
<div name="RawHtml">
134+
Raw html blocks may also be nested.
135+
</div>
136+
137+
</div>
138+
<p>This text is after the markdown in html.</p>
139+
```

markdown/extensions/extra.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,32 @@
66
[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/).
77
88
Note that each of the individual extensions still need to be available
9-
on your PYTHONPATH. This extension simply wraps them all up as a
9+
on your PYTHONPATH. This extension simply wraps them all up as a
1010
convenience so that only one extension needs to be listed when
1111
initiating Markdown. See the documentation for each individual
1212
extension for specifics about that extension.
1313
14-
In the event that one or more of the supported extensions are not
15-
available for import, Markdown will issue a warning and simply continue
16-
without that extension.
14+
In the event that one or more of the supported extensions are not
15+
available for import, Markdown will issue a warning and simply continue
16+
without that extension.
1717
18-
There may be additional extensions that are distributed with
18+
There may be additional extensions that are distributed with
1919
Python-Markdown that are not included here in Extra. Those extensions
2020
are not part of PHP Markdown Extra, and therefore, not part of
2121
Python-Markdown Extra. If you really would like Extra to include
2222
additional extensions, we suggest creating your own clone of Extra
23-
under a differant name. You could also edit the `extensions` global
24-
variable defined below, but be aware that such changes may be lost
23+
under a differant name. You could also edit the `extensions` global
24+
variable defined below, but be aware that such changes may be lost
2525
when you upgrade to any future version of Python-Markdown.
2626
2727
"""
2828

2929
from __future__ import absolute_import
3030
from __future__ import unicode_literals
3131
from . import Extension
32+
from ..blockprocessors import BlockProcessor
33+
from .. import util
34+
import re
3235

3336
extensions = ['smart_strong',
3437
'fenced_code',
@@ -38,7 +41,7 @@
3841
'tables',
3942
'abbr',
4043
]
41-
44+
4245

4346
class ExtraExtension(Extension):
4447
""" Add various extensions to Markdown class."""
@@ -49,6 +52,80 @@ def extendMarkdown(self, md, md_globals):
4952
if not md.safeMode:
5053
# Turn on processing of markdown text within raw html
5154
md.preprocessors['html_block'].markdown_in_raw = True
55+
md.parser.blockprocessors.add('markdown_block',
56+
MarkdownInHtmlProcessor(md.parser),
57+
'_begin')
58+
md.parser.blockprocessors.tag_counter = -1
59+
md.parser.blockprocessors.contain_span_tags = re.compile(
60+
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
61+
5262

5363
def makeExtension(configs={}):
5464
return ExtraExtension(configs=dict(configs))
65+
66+
67+
class MarkdownInHtmlProcessor(BlockProcessor):
68+
"""Process Markdown Inside HTML Blocks."""
69+
def test(self, parent, block):
70+
return block == util.TAG_PLACEHOLDER % \
71+
str(self.parser.blockprocessors.tag_counter + 1)
72+
73+
def _process_nests(self, element, block):
74+
"""Process the element's child elements in self.run."""
75+
# Build list of indexes of each nest within the parent element.
76+
nest_index = [] # a list of tuples: (left index, right index)
77+
i = self.parser.blockprocessors.tag_counter + 1
78+
while len(self.parser.markdown.htmlStash.tag_data) > i and self.\
79+
parser.markdown.htmlStash.tag_data[i]['left_index']:
80+
left_child_index = \
81+
self.parser.markdown.htmlStash.tag_data[i]['left_index']
82+
right_child_index = \
83+
self.parser.markdown.htmlStash.tag_data[i]['right_index']
84+
nest_index.append((left_child_index - 1, right_child_index))
85+
i += 1
86+
87+
# Create each nest subelement.
88+
i = 0
89+
for n in nest_index[:-1]:
90+
self.run(element, block[n[0]:n[1]],
91+
block[n[1]:nest_index[i + 1][0]], True)
92+
i += 1
93+
self.run(element, block[nest_index[-1][0]:nest_index[-1][1]], # last
94+
block[nest_index[-1][1]:], True) # nest
95+
96+
def run(self, parent, blocks, tail=None, nest=False):
97+
self.parser.blockprocessors.tag_counter += 1
98+
tag_data = self.parser.markdown.htmlStash.tag_data[
99+
self.parser.blockprocessors.tag_counter]
100+
101+
# Create Element
102+
markdown_value = tag_data['attrs'].pop('markdown')
103+
element = util.etree.SubElement(parent, tag_data['tag'],
104+
tag_data['attrs'])
105+
106+
# Slice Off Block
107+
if nest:
108+
self.parser.parseBlocks(parent, tail) # Process Tail
109+
block = blocks[1:]
110+
else: # includes nests since a third level of nesting isn't supported
111+
block = blocks[tag_data['left_index'] + 1:
112+
tag_data['right_index']]
113+
del blocks[:tag_data['right_index']]
114+
115+
# Process Text
116+
if (self.parser.blockprocessors.contain_span_tags.match( # Span Mode
117+
tag_data['tag']) and markdown_value != 'block') or \
118+
markdown_value == 'span':
119+
element.text = '\n'.join(block)
120+
else: # Block Mode
121+
i = self.parser.blockprocessors.tag_counter + 1
122+
if len(self.parser.markdown.htmlStash.tag_data) > i and self.\
123+
parser.markdown.htmlStash.tag_data[i]['left_index']:
124+
first_subelement_index = self.parser.markdown.htmlStash.\
125+
tag_data[i]['left_index'] - 1
126+
self.parser.parseBlocks(
127+
element, block[:first_subelement_index])
128+
if not nest:
129+
block = self._process_nests(element, block)
130+
else:
131+
self.parser.parseBlocks(element, block)

0 commit comments

Comments
 (0)