├── .gitignore
├── LICENSE
├── README.org
├── README.rst
├── orgpython
├── __init__.py
├── document.py
├── inline.py
└── src.py
├── setup.py
└── test.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017-2020, honmaple
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | * org-python
2 | An orgmode parser for converting orgmode to html based on python.
3 |
4 | [[https://pypi.python.org/pypi/org-python][https://img.shields.io/badge/pypi-v0.3.2-brightgreen.svg]]
5 | [[https://python.org][https://img.shields.io/badge/python-3-brightgreen.svg]]
6 | [[LICENSE][https://img.shields.io/badge/license-BSD-blue.svg]]
7 |
8 | ** quickstart
9 | #+BEGIN_SRC sh
10 | pip install org-python
11 | #+END_SRC
12 |
13 | #+BEGIN_SRC python
14 | from orgpython import to_html
15 |
16 | text = '''* heading
17 | - list1
18 | - list2
19 | - list3
20 | - list4
21 | - list5
22 |
23 | | th1-1 | th1-2 | th1-3 |
24 | |--------+--------+--------|
25 | | row1-1 | row1-2 | row1-3 |
26 | | row2-1 | row2-2 | row2-3 |
27 | | row3-1 | row3-2 | row3-3 |
28 | '''
29 | print(to_html(text, toc=True, offset=0, highlight=True))
30 | #+END_SRC
31 |
32 | ** feature
33 | - [X] toc
34 | - [X] heading
35 | #+BEGIN_EXAMPLE
36 | * headind 1
37 | ** headind 2
38 | *** headind 3
39 | **** headind 4
40 | ***** headind 5
41 | ****** headind 6
42 | #+END_EXAMPLE
43 | - [X] unordered_list
44 | #+BEGIN_EXAMPLE
45 | - list
46 | - list
47 | - list
48 | + list
49 | - list
50 | #+END_EXAMPLE
51 | - [X] ordered_list
52 | #+BEGIN_EXAMPLE
53 | 1. list
54 | 2. list
55 | 3. list
56 | #+END_EXAMPLE
57 | - [X] bold
58 | #+BEGIN_EXAMPLE
59 | *bold*
60 | #+END_EXAMPLE
61 | - [X] italic
62 | #+BEGIN_EXAMPLE
63 | **italic**
64 | #+END_EXAMPLE
65 | - [X] underlined
66 | #+BEGIN_EXAMPLE
67 | _italic_
68 | #+END_EXAMPLE
69 | - [X] code
70 | #+BEGIN_EXAMPLE
71 | =code=
72 | #+END_EXAMPLE
73 | - [X] delete
74 | #+BEGIN_EXAMPLE
75 | +delete+
76 | #+END_EXAMPLE
77 | - [X] image
78 | #+BEGIN_EXAMPLE
79 | [[src][alt]]
80 | #+END_EXAMPLE
81 | - [X] link
82 | #+BEGIN_EXAMPLE
83 | [[href][text]]
84 | #+END_EXAMPLE
85 | - [X] begin_example
86 | - [X] begin_src
87 | - [X] begin_quote
88 | - [X] table
89 | #+BEGIN_EXAMPLE
90 | | th1-1 | th1-2 | th1-3 |
91 | |--------+--------+--------|
92 | | row1-1 | row1-2 | row1-3 |
93 | | row2-1 | row2-2 | row2-3 |
94 | | row3-1 | row3-2 | row3-3 |
95 | #+END_EXAMPLE
96 |
97 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | :Author: jianglin
2 |
3 | .. contents::
4 |
5 | 1 org-python
6 | ------------
7 |
8 | An orgmode parser for converting orgmode to html based on python.
9 |
10 | .. image:: https://img.shields.io/badge/pypi-v0.3.2-brightgreen.svg
11 | :target: https://pypi.python.org/pypi/org-python
12 | .. image:: https://img.shields.io/badge/python-3-brightgreen.svg
13 | :target: https://python.org
14 | .. image:: https://img.shields.io/badge/license-BSD-blue.svg
15 | :target: LICENSE
16 |
17 | 1.1 quickstart
18 | ~~~~~~~~~~~~~~
19 |
20 | .. code:: sh
21 |
22 | pip install org-python
23 |
24 | .. code:: python
25 |
26 | from orgpython import to_html
27 |
28 | text = '''* heading
29 | - list1
30 | - list2
31 | - list3
32 | - list4
33 | - list5
34 |
35 | | th1-1 | th1-2 | th1-3 |
36 | |--------+--------+--------|
37 | | row1-1 | row1-2 | row1-3 |
38 | | row2-1 | row2-2 | row2-3 |
39 | | row3-1 | row3-2 | row3-3 |
40 | '''
41 | print(to_html(text, toc=True, offset=0, highlight=True))
42 |
43 | 1.2 feature
44 | ~~~~~~~~~~~
45 |
46 | - ☑ toc
47 |
48 | - ☑ heading
49 |
50 | ::
51 |
52 | * headind 1
53 | ** headind 2
54 | *** headind 3
55 | **** headind 4
56 | ***** headind 5
57 | ****** headind 6
58 |
59 | - ☑ unordered\_list
60 |
61 | ::
62 |
63 | - list
64 | - list
65 | - list
66 | + list
67 | - list
68 |
69 | - ☑ ordered\_list
70 |
71 | ::
72 |
73 | 1. list
74 | 2. list
75 | 3. list
76 |
77 | - ☑ bold
78 |
79 | ::
80 |
81 | *bold*
82 |
83 | - ☑ italic
84 |
85 | ::
86 |
87 | **italic**
88 |
89 | - ☑ underlined
90 |
91 | ::
92 |
93 | _italic_
94 |
95 | - ☑ code
96 |
97 | ::
98 |
99 | =code=
100 |
101 | - ☑ delete
102 |
103 | ::
104 |
105 | +delete+
106 |
107 | - ☑ image
108 |
109 | ::
110 |
111 | [[src][alt]]
112 |
113 | - ☑ link
114 |
115 | ::
116 |
117 | [[href][text]]
118 |
119 | - ☑ begin\_example
120 |
121 | - ☑ begin\_src
122 |
123 | - ☑ begin\_quote
124 |
125 | - ☑ table
126 |
127 | ::
128 |
129 | | th1-1 | th1-2 | th1-3 |
130 | |--------+--------+--------|
131 | | row1-1 | row1-2 | row1-3 |
132 | | row2-1 | row2-2 | row2-3 |
133 | | row3-1 | row3-2 | row3-3 |
134 |
--------------------------------------------------------------------------------
/orgpython/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ********************************************************************************
4 | # Copyright © 2017-2020 jianglin
5 | # File Name: __init__.py
6 | # Author: jianglin
7 | # Email: mail@honmaple.com
8 | # Created: 2019-05-29 18:06:22 (CST)
9 | # Last Update: Sunday 2020-08-16 19:45:09 (CST)
10 | # By:
11 | # Description:
12 | # ********************************************************************************
13 | from .document import Document
14 |
15 |
16 | def to_text(content, **kwargs):
17 | return Document(content, **kwargs).to_text()
18 |
19 |
20 | def to_html(content, **kwargs):
21 | return Document(content, **kwargs).to_html()
22 |
23 |
24 | def to_markdown(content, **kwargs):
25 | return Document(content, **kwargs).to_markdown()
26 |
--------------------------------------------------------------------------------
/orgpython/document.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ********************************************************************************
4 | # Copyright © 2017-2020 jianglin
5 | # File Name: document.py
6 | # Author: jianglin
7 | # Email: mail@honmaple.com
8 | # Created: 2018-02-26 11:44:43 (CST)
9 | # Last Update: Wednesday 2020-08-19 12:00:03 (CST)
10 | # Description:
11 | # ********************************************************************************
12 | import re
13 | from hashlib import sha1
14 | from textwrap import dedent
15 |
16 | from .inline import Blankline, Hr, InlineText
17 | from .src import highlight as src_highlight
18 |
19 | DRAWER_BEGIN_REGEXP = re.compile(r"^(\s*):(\S+):\s*$")
20 | DRAWER_END_REGEXP = re.compile(r"^(\s*):END:\s*$")
21 | DRAWER_PROPERTY_REGEXP = re.compile(r"^(\s*):(\S+):(\s+(.*)$|$)")
22 |
23 | BLOCK_BEGIN_REGEXP = re.compile(r"(?i)^(\s*)#\+BEGIN_(\w+)(.*)")
24 | BLOCK_END_REGEXP = re.compile(r"(?i)^(\s*)#\+END_(\w+)")
25 | BLOCK_RESULT_REGEXP = re.compile(r"(?i)^(\s*)#\+RESULTS:")
26 | BLOCK_RESULT_CONTENT_REGEXP = re.compile(r"(?:^|\s+):(\s+(.*)|$)")
27 |
28 | TABLE_SEP_REGEXP = re.compile(r"^(\s*)(\|[+-|]*)\s*$")
29 | TABLE_ROW_REGEXP = re.compile(r"^(\s*)(\|.*)")
30 | TABLE_ALIGN_REGEXP = re.compile(r"^<(l|c|r)>$")
31 |
32 | LIST_DESCRIPTIVE_REGEXP = re.compile(r"^(\s*)([+*-])\s+(.*)::(\s|$)")
33 | LIST_UNORDER_REGEXP = re.compile(r"^(\s*)([+*-])(\s+(.*)|$)")
34 | LIST_ORDER_REGEXP = re.compile(r"^(\s*)(([0-9]+|[a-zA-Z])[.)])(\s+(.*)|$)")
35 | LIST_STATUS_REGEXP = re.compile(r"\[( |X|-)\]\s")
36 | LIST_LEVEL_REGEXP = re.compile(r"(\s*)(.+)$")
37 |
38 | HEADLINE_REGEXP = re.compile(
39 | r"^(\*+)(?:\s+(.+?))?(?:\s+\[#(.+)\])?(\s+.*?)(?:\s+:(.+):)?$")
40 | KEYWORD_REGEXP = re.compile(r"^(\s*)#\+([^:]+):(\s+(.*)|$)")
41 | COMMENT_REGEXP = re.compile(r"^(\s*)#(.*)")
42 | ATTRIBUTE_REGEXP = re.compile(r"(?:^|\s+)(:[-\w]+)\s+(.*)$")
43 |
44 | TODO_KEYWORDS = ("DONE", "TODO")
45 |
46 |
47 | def string_split(s, sep):
48 | if not s:
49 | return []
50 | return s.split(sep)
51 |
52 |
53 | class Parser(object):
54 | def __init__(self, content=""):
55 | self.lines = content.splitlines()
56 | self.level = 0
57 | self.element = ""
58 | self.children = []
59 | self.escape = True
60 | self.needparse = True
61 | self.parsed_nodes = (
62 | "blankline",
63 | "headline",
64 | "table",
65 | "list",
66 | "drawer",
67 | "block",
68 | "block_result",
69 | "keyword",
70 | "hr",
71 | )
72 |
73 | def first_child(self):
74 | if len(self.children) == 0:
75 | return
76 | return self.children[0]
77 |
78 | def last_child(self):
79 | if len(self.children) == 0:
80 | return
81 | return self.children[-1]
82 |
83 | def add_child(self, node):
84 | last = self.last_child()
85 | if self.is_headline(last):
86 | if self.is_properties(node):
87 | last.properties = node
88 | return
89 |
90 | if not self.is_headline(node):
91 | last.add_child(node)
92 | return
93 |
94 | if self.is_headline(node) and node.stars > last.stars:
95 | last.add_child(node)
96 | return
97 |
98 | if self.is_table(last):
99 | if self.is_table(node):
100 | last.add_child(node)
101 | return
102 |
103 | if self.is_list(last):
104 | if self.is_blankline(node):
105 | last.add_child(node)
106 | return
107 |
108 | if node.level > last.level:
109 | last.add_child(node)
110 | return
111 |
112 | if self.is_list(node) and node.level == last.level:
113 | last.add_child(node)
114 | return
115 |
116 | if self.is_keyword(last):
117 | if self.is_table(node):
118 | node.keyword = last
119 |
120 | if self.is_paragraph(last):
121 | if self.is_inlinetext(node):
122 | last.add_child(node)
123 | return
124 |
125 | if self.is_inlinetext(node):
126 | self.children.append(self.paragraph(node))
127 | return
128 |
129 | self.children.append(node)
130 |
131 | def is_keyword(self, child):
132 | return child and isinstance(child, Keyword)
133 |
134 | def is_headline(self, child):
135 | return child and isinstance(child, Headline)
136 |
137 | def is_list(self, child):
138 | return child and isinstance(child, List)
139 |
140 | def is_table(self, child):
141 | return child and isinstance(child, Table)
142 |
143 | def is_src(self, child):
144 | return child and isinstance(child, (Src, Example))
145 |
146 | def is_inlinetext(self, child):
147 | return child and isinstance(child, InlineText)
148 |
149 | def is_blankline(self, child):
150 | return child and isinstance(child, Blankline)
151 |
152 | def is_paragraph(self, child):
153 | return child and isinstance(child, Paragraph)
154 |
155 | def is_properties(self, child):
156 | return child and isinstance(child, Properties)
157 |
158 | def inlinetext(self, text):
159 | return InlineText(text, self.needparse, self.escape)
160 |
161 | def paragraph(self, node):
162 | n = Paragraph()
163 | n.add_child(node)
164 | return n
165 |
166 | def _parse_paired(self, cls, index, lines):
167 | node = cls.match(lines[index])
168 | if not node:
169 | return None, index
170 |
171 | end = len(lines)
172 | num = index + 1
173 | while num < end:
174 | if node.matchend(num, lines):
175 | node.preparse(lines[index + 1:num])
176 | return node, num
177 | num += 1
178 | return None, index
179 |
180 | def _parse_nopaired(self, cls, index, lines):
181 | node = cls.match(lines[index])
182 | if not node:
183 | return None, index
184 |
185 | end = len(lines)
186 | num = index + 1
187 | while num < end:
188 | if node.matchend(num, lines):
189 | break
190 | num += 1
191 | node.preparse(lines[index + 1:num])
192 | return node, num
193 |
194 | def parse_headline(self, index, lines):
195 | return Headline.match(lines[index]), index
196 |
197 | def parse_list(self, index, lines):
198 | return List.match(lines[index]), index
199 |
200 | def parse_table(self, index, lines):
201 | return self._parse_nopaired(Table, index, lines)
202 |
203 | def parse_drawer(self, index, lines):
204 | return self._parse_paired(Drawer, index, lines)
205 |
206 | def parse_block(self, index, lines):
207 | return self._parse_paired(Block, index, lines)
208 |
209 | def parse_block_result(self, index, lines):
210 | return self._parse_paired(BlockResult, index, lines)
211 |
212 | def parse_blankline(self, index, lines):
213 | return Blankline.match(lines[index]), index
214 |
215 | def parse_keyword(self, index, lines):
216 | return Keyword.match(lines[index]), index
217 |
218 | def parse_hr(self, index, lines):
219 | return Hr.match(lines[index]), index
220 |
221 | def parse_inlinetext(self, index, lines):
222 | return self.inlinetext(lines[index]), index
223 |
224 | def parse(self, index, lines):
225 | for b in self.parsed_nodes:
226 | func = "parse_" + b
227 | if not hasattr(self, func):
228 | continue
229 | block, num = getattr(self, func)(index, lines)
230 | if not block:
231 | continue
232 | return block, num
233 |
234 | return self.parse_inlinetext(index, lines)
235 |
236 | def preparse(self, lines):
237 | index = 0
238 | while index < len(lines):
239 | line = lines[index]
240 | node, index = self.parse(index, lines)
241 | if node:
242 | node.level = len(line) - len(line.strip())
243 | self.add_child(node)
244 | index += 1
245 |
246 | def to_html(self):
247 | if len(self.children) == 0 and len(self.lines) > 0:
248 | self.preparse(self.lines)
249 |
250 | children = []
251 | for child in self.children:
252 | content = child.to_html()
253 | if not content:
254 | continue
255 | children.append(content)
256 | text = "\n".join(children)
257 | if self.element:
258 | return self.element.format(text)
259 | return text
260 |
261 | def __str__(self):
262 | str_children = [str(child) for child in self.children]
263 | return self.__class__.__name__ + '(' + ','.join(str_children) + ')'
264 |
265 | def __repr__(self):
266 | return self.__str__()
267 |
268 |
269 | class Headline(Parser):
270 | def __init__(
271 | self,
272 | title,
273 | stars=1,
274 | keyword=None,
275 | priority=None,
276 | tags=[],
277 | todo_keywords=TODO_KEYWORDS):
278 | super(Headline, self).__init__()
279 | self.title = title
280 | self.stars = stars
281 | self.keyword = keyword
282 | self.priority = priority
283 | self.tags = tags
284 | self.properties = None
285 | self.todo_keywords = todo_keywords
286 |
287 | @classmethod
288 | def match(cls, line):
289 | match = HEADLINE_REGEXP.match(line)
290 | if not match:
291 | return
292 |
293 | stars = len(match[1])
294 | keyword = match[2] or ""
295 | priority = match[3] or ""
296 |
297 | if keyword and not priority:
298 | if len(keyword) >= 4 and keyword[0:2] == "[#":
299 | priority = keyword[2:-1]
300 | keyword = ""
301 |
302 | title = keyword + match[4]
303 | keyword = ""
304 |
305 | return cls(
306 | title,
307 | stars,
308 | keyword,
309 | priority,
310 | string_split(match[5], ":"),
311 | )
312 |
313 | def id(self):
314 | hid = 'org-{0}'.format(sha1(self.title.encode()).hexdigest()[:10])
315 | if self.properties:
316 | return self.properties.get("CUSTOM_ID", hid)
317 | return hid
318 |
319 | def toc(self):
320 | b = ""
321 | if self.keyword:
322 | b = b + "{0}".format(self.keyword)
323 | if self.priority:
324 | b = b + "{0}".format(self.priority)
325 |
326 | b = b + self.inlinetext(self.title).to_html()
327 |
328 | for tag in self.tags:
329 | b = b + "{0}".format(tag)
330 | return b.strip()
331 |
332 | def to_html(self):
333 | b = "
\n{0}\n
" 423 | 424 | def add_child(self, node): 425 | self.children.append(node) 426 | 427 | def to_html(self): 428 | children = [child.to_html() for child in self.children] 429 | return self.element.format("\n{0}\n" 436 | 437 | 438 | class Export(Block): 439 | def __init__(self, language="", params=""): 440 | super(Export, self).__init__("export", params) 441 | self.language = language 442 | self.escape = self.language.upper() != "HTML" 443 | self.parsed_nodes = () 444 | 445 | def to_html(self): 446 | if not self.escape: 447 | return super(Export, self).to_html() 448 | return "" 449 | 450 | 451 | class Src(Block): 452 | def __init__(self, language="", params="", highlight=False): 453 | super(Src, self).__init__("src", params) 454 | self.language = language 455 | self.highlight_code = highlight 456 | self.element = "
\n{1}\n" 457 | self.needparse = False 458 | self.escape = False 459 | self.parsed_nodes = () 460 | 461 | def add_child(self, node): 462 | self.children.append(node) 463 | 464 | def highlight(self, language, text): 465 | return src_highlight(language, text) 466 | 467 | def to_html(self): 468 | text = "\n".join([child.to_html() for child in self.children]) 469 | if self.highlight_code: 470 | return self.highlight(self.language, dedent(text)) 471 | if not self.language: 472 | return "
\n{0}\n".format(dedent(text)) 473 | return self.element.format(self.language, dedent(text)) 474 | 475 | 476 | class Example(Src): 477 | def __init__(self, params="", highlight=False): 478 | super(Example, self).__init__("example", params, highlight) 479 | self.name = "example" 480 | 481 | 482 | class BlockResult(Parser): 483 | def __init__(self): 484 | super(BlockResult, self).__init__() 485 | self.element = "
\n{0}\n" 486 | 487 | @classmethod 488 | def match(cls, line): 489 | match = BLOCK_RESULT_REGEXP.match(line) 490 | if not match: 491 | return 492 | return cls() 493 | 494 | def matchend(self, index, lines): 495 | return not BLOCK_RESULT_CONTENT_REGEXP.match(lines[index]) 496 | 497 | def parse(self, index, lines): 498 | match = BLOCK_RESULT_CONTENT_REGEXP.match(lines[index]) 499 | return self.inlinetext(match[2]), index 500 | 501 | 502 | class ListItem(Parser): 503 | def __init__(self, status=None, checkbox="HTML"): 504 | super(ListItem, self).__init__() 505 | self.status = status 506 | self.checkbox = checkbox 507 | self.element = "
\n{0}\n
" 752 | self.parsed_nodes = () 753 | 754 | def add_child(self, node): 755 | self.children.append(node) 756 | 757 | 758 | class Section(Parser): 759 | def __init__(self, headline): 760 | super(Section, self).__init__() 761 | self.headline = headline 762 | 763 | @property 764 | def stars(self): 765 | return self.headline.stars 766 | 767 | def add_child(self, node): 768 | last = self.last_child() 769 | if not last: 770 | self.children.append(node) 771 | return 772 | 773 | if node.stars > last.stars: 774 | last.add_child(node) 775 | return 776 | self.children.append(node) 777 | 778 | def to_html(self): 779 | text = "{0}
"
272 |
273 | @classmethod
274 | def match(cls, line, index):
275 | return match_emphasis(cls, CODE_REGEXP, line, index)
276 |
277 |
278 | class Italic(InlineParser):
279 | def __init__(self, content):
280 | super(Italic, self).__init__(content)
281 | self.element = "{0}"
282 |
283 | @classmethod
284 | def match(cls, line, index):
285 | return match_emphasis(cls, ITALIC_REGEXP, line, index)
286 |
287 |
288 | class Delete(InlineParser):
289 | def __init__(self, content):
290 | super(Delete, self).__init__(content)
291 | self.element = "{0}
"
302 |
303 | @classmethod
304 | def match(cls, line, index):
305 | return match_emphasis(cls, VERBATIM_REGEXP, line, index)
306 |
307 |
308 | class Underline(InlineParser):
309 | def __init__(self, content):
310 | super(Underline, self).__init__(content)
311 | self.element = "{0}"
312 |
313 | @classmethod
314 | def match(cls, line, index):
315 | return match_emphasis(cls, UNDERLINE_REGEXP, line, index)
316 |
317 |
318 | class Percent(InlineParser):
319 | def __init__(self, content):
320 | super(Percent, self).__init__(content)
321 | self.element = "[{0}]
"
322 |
323 | @classmethod
324 | def match(cls, line, index):
325 | match = PERCENT_REGEXP.match(line, index)
326 | if not match:
327 | return None, index
328 | return cls(match[1]), match.end()
329 |
330 |
331 | class Link(InlineParser):
332 | def __init__(self, url, desc=None):
333 | super(Link, self).__init__(url)
334 | self.desc = desc
335 |
336 | @classmethod
337 | def match(cls, line, index):
338 | match = LINK_REGEXP.match(line, index)
339 | if not match:
340 | return None, index
341 | return cls(match[1], match[2]), match.end()
342 |
343 | def is_img(self):
344 | _, ext = os.path.splitext(self.content)
345 | return not self.desc and IMG_REGEXP.match(ext)
346 |
347 | def is_vedio(self):
348 | _, ext = os.path.splitext(self.content)
349 | return not self.desc and VIDEO_REGEXP.match(ext)
350 |
351 | def to_html(self):
352 | if self.is_img():
353 | return "