From ef3017718986b871a1fd36a000a6604bbd4cb761 Mon Sep 17 00:00:00 2001 From: melpon Date: Sat, 27 Sep 2014 22:27:43 +0900 Subject: [PATCH 01/72] Initial commit --- .gitignore | 1 + __init__.py | 0 footer.py | 39 ++++++ html_attribute.py | 262 +++++++++++++++++++++++++++++++++++++++ qualified_fenced_code.py | 248 ++++++++++++++++++++++++++++++++++++ 5 files changed, 550 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 footer.py create mode 100644 html_attribute.py create mode 100644 qualified_fenced_code.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/footer.py b/footer.py new file mode 100644 index 0000000..2b6a0e1 --- /dev/null +++ b/footer.py @@ -0,0 +1,39 @@ +#coding: utf-8 +import markdown +from markdown.util import etree + +class FooterExtension(markdown.Extension): + """ Footer Extension. """ + + def __init__(self, configs): + # デフォルトの設定 + self.config = { + 'url' : [None, 'URL'], + } + + # ユーザ設定で上書き + for key, value in configs: + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + footer = FooterTreeprocessor() + footer.config = self.getConfigs() + md.registerExtension(self) + md.treeprocessors.add('footer', footer, '_begin') + +class FooterTreeprocessor(markdown.treeprocessors.Treeprocessor): + """ Build and append footnote div to end of document. """ + + def _make_footer(self): + footer = etree.Element('footer') + a = etree.SubElement(footer, 'a') + a.set('href', self.config['url']) + a.text = u'編集' + return footer + + def run(self, root): + footer = self._make_footer() + root.append(footer) + +def makeExtension(configs=[]): + return FooterExtension(configs=configs) diff --git a/html_attribute.py b/html_attribute.py new file mode 100644 index 0000000..3861397 --- /dev/null +++ b/html_attribute.py @@ -0,0 +1,262 @@ +#coding: utf-8 +from __future__ import unicode_literals +""" +markdown から変換した HTML に属性を追加する +""" + +import re +from markdown.util import etree +from markdown import postprocessors +import markdown + +HTML_TAGS = { + 'html', + 'head', + 'title', + 'base', + 'link', + 'meta', + 'style', + 'script', + 'noscript', + 'body', + 'section', + 'nav', + 'article', + 'aside', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hgroup', + 'header', + 'footer', + 'address', + 'p', + 'hr', + 'pre', + 'blockquote', + 'ol', + 'ul', + 'li', + 'dl', + 'dt', + 'dd', + 'figure', + 'figcaption', + 'div', + 'a', + 'em', + 'strong', + 'small', + 's', + 'cite', + 'q', + 'dfn', + 'abbr', + 'time', + 'code', + 'var', + 'samp', + 'kbd', + 'sub', + 'sup', + 'i', + 'b', + 'u', + 'mark', + 'ruby', + 'rt', + 'rp', + 'bdi', + 'bdo', + 'span', + 'br', + 'wbr', + 'ins', + 'del', + 'img', + 'iframe', + 'embed', + 'object', + 'param', + 'video', + 'audio', + 'source', + 'track', + 'canvas', + 'map', + 'area', + 'table', + 'caption', + 'colgroup', + 'col', + 'tbody', + 'thead', + 'tfoot', + 'tr', + 'td', + 'th', + 'form', + 'fieldset', + 'legend', + 'label', + 'input', + 'button', + 'select', + 'datalist', + 'optgroup', + 'option', + 'textarea', + 'keygen', + 'output', + 'progress', + 'meter', + 'details', + 'summary', + 'command', + 'menu', +} + +class SafeRawHtmlPostprocessor(postprocessors.Postprocessor): + def run(self, text): + for i in range(self.markdown.htmlStash.html_counter): + html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] + if not safe: + html = self.escape(html) + text = text.replace(self.markdown.htmlStash.get_placeholder(i), html) + return text + + def escape(self, html): + # html tag + m = re.match(r'^\<\/?([a-zA-Z0-9]+)[^\>]*\>$', html) + if m: + if m.group(1) in HTML_TAGS: + return html + # html entity + m = re.match(r'^\&.*\;$', html) + if m: + return html + return self.basic_escape(html) + + def basic_escape(self, html): + html = html.replace('&', '&') + html = html.replace('<', '<') + html = html.replace('>', '>') + return html.replace('"', '"') + +class AttributePostprocessor(postprocessors.Postprocessor): + def __init__(self, md): + postprocessors.Postprocessor.__init__(self, md) + self._markdown = md + + def _iterate(self, elements, f): + f(elements) + for child in elements.getchildren(): + self._iterate(child, f) + + def _add_color_code(self, element): + if element.tag == 'code': + text = element.text + element.text = '' + e = etree.SubElement(element, 'span', style='color: #000') + e.text = text + + def _add_border_table(self, element): + if element.tag == 'table': + element.attrib['border'] = '1' + element.attrib['bordercolor'] = '#888' + element.attrib['style'] = 'border-collapse:collapse' + + def _remove_md(self, url): + # サイト内絶対パスで末尾に .md があった場合、取り除く + # (github のプレビューとの互換性のため) + matched = re.match('([^#]*)\.md(#.*)?$', url) + if matched: + url = matched.group(1) + anchor = matched.group(2) + if anchor is not None: + url = url + anchor + return url + + def _to_absolute_url(self, element): + if element.tag == 'a' and element.attrib.has_key('href'): + base_url = self.config['base_url'].strip('/') + base_paths = self.config['base_path'].strip('/').split('/') + full_path = self.config['full_path'] + + url = element.attrib['href'] + if url.startswith('http://') or url.startswith('https://'): + # 絶対パス + base_url_body = base_url.split('//', 2)[1] + url_body = url.split('//', 2)[1] + # 別ドメインの場合は別タブで開く + if not url_body.startswith(base_url_body): + element.attrib['target'] = '_blank' + elif url.startswith('/'): + # サイト内絶対パス + element.attrib['href'] = base_url + url + element.attrib['href'] = self._remove_md(element.attrib['href']) + elif url.startswith('#'): + # ページ内リンク + element.attrib['href'] = base_url + '/' + self._remove_md(full_path) + url + else: + # サイト内相対パス + paths = [] + for p in base_paths + url.split('/'): + if p == '': + continue + elif p == '.': + continue + elif p == '..': + paths = paths[:-1] + else: + paths.append(p) + element.attrib['href'] = base_url + '/' + '/'.join(paths) + element.attrib['href'] = self._remove_md(element.attrib['href']) + + def run(self, text): + text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) + root = etree.fromstring(text.encode('utf-8')) + self._iterate(root, self._add_color_code) + self._iterate(root, self._add_border_table) + self._iterate(root, self._to_absolute_url) + + output = self._markdown.serializer(root) + if self._markdown.stripTopLevelTags: + try: + start = output.index('<%s>'%self._markdown.doc_tag)+len(self._markdown.doc_tag)+2 + end = output.rindex(''%self._markdown.doc_tag) + output = output[start:end].strip() + except ValueError: + if output.strip().endswith('<%s />'%self._markdown.doc_tag): + # We have an empty document + output = '' + else: + # We have a serious problem + raise ValueError('Markdown failed to strip top-level tags. Document=%r' % output.strip()) + return output + +class AttributeExtension(markdown.Extension): + def __init__(self, configs): + # デフォルトの設定 + self.config = { + 'base_url' : [None, "Base URL used to link URL as absolute URL"], + 'base_path' : [None, "Base Path used to link URL as relative URL"], + 'full_path' : [None, "Full Path used to link URL as anchor URL"], + } + + # ユーザ設定で上書き + for key, value in configs: + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + attr = AttributePostprocessor(md) + attr.config = self.getConfigs() + md.postprocessors.add('html_attribute', attr, '_end') + md.postprocessors['raw_html'] = SafeRawHtmlPostprocessor(md) + +def makeExtension(configs): + return AttributeExtension(configs) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py new file mode 100644 index 0000000..20cbad3 --- /dev/null +++ b/qualified_fenced_code.py @@ -0,0 +1,248 @@ +#coding: utf-8 +from __future__ import unicode_literals +""" +Fenced Code Extension の改造版 +========================================= + +github でのコードブロック記法が使える。 + + >>> text = ''' + ... ````` + ... # コードをここに書く + ... x = 10 + ... `````''' + >>> print markdown.markdown(text, extensions=['qualified_fenced_code']) +
# コードをここに書く
+    x = 10
+    
+ +かつ、これらのコードに修飾ができる。 + + >>> text = ''' + ... ``` + ... x = [3, 2, 1] + ... y = sorted(x) + ... x.sort() + ... ``` + ... sorted[color ff0000] + ... sort[link http://example.com/] + ... ''' + >>> print markdown.markdown(text, extensions=['qualified_fenced_code']) +""" + +from __future__ import absolute_import +import re +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor +from markdown.util import Processor +from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension + +CODE_WRAP = '
%s
' +LANG_TAG = ' class="%s"' + +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(\n|(?P.*?\n\s*\n))', re.MULTILINE|re.DOTALL) +QUALIFY_RE = re.compile(r'^\* +(?P.*?)(?P(\[.*?\])*)$') +QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') + + +class QualifiedFencedCodeExtension(Extension): + def extendMarkdown(self, md, md_globals): + fenced_block = QualifiedFencedBlockPreprocessor(md) + md.registerExtension(self) + + md.preprocessors.add('qualified_fenced_code', fenced_block, ">normalize_whitespace") + + +def _make_random_string(): + """アルファベットから成るランダムな文字列を作る + """ + import string + from random import randrange + alphabets = string.ascii_letters + return ''.join(alphabets[randrange(len(alphabets))] for i in xrange(32)) + +class QualifyDictionary(object): + def __init__(self): + # 各コマンドに対する実際の処理 + def _qualify_italic(*xs): + return '{0}'.format(*xs) + def _qualify_color(*xs): + return '{0}'.format(*xs) + def _qualify_link(*xs): + text = xs[0] + url = xs[1] + return '{0}'.format(*xs) + + self.qualify_dic = { + 'italic': _qualify_italic, + 'color': _qualify_color, + 'link': _qualify_link, + } + +class Qualifier(object): + """修飾1個分のデータを保持するクラス + """ + def __init__(self, line, qdic): + command_res = [r'(\[{cmd}(\]|.*?\]))'.format(cmd=cmd) for cmd in qdic.qualify_dic] + + qualify_re_str = r'^\* +(?P.*?)(?P({commands})+)$'.format( + commands='|'.join(command_res)) + qualify_re = re.compile(qualify_re_str) + + # parsing + m = qualify_re.search(line) + if not m: + raise ValueError, 'Failed parse' + self.target = m.group('target') + self.commands = [] + def f(match): + self.commands.append(match.group(1)) + QUALIFY_COMMAND_RE.sub(f, m.group('commands')) + +class QualifierList(object): + def __init__(self, lines): + self._qdic = QualifyDictionary() + + # Qualifier を作るが、エラーになったデータは取り除く + def ignore(f, *args, **kwargs): + try: + return f(*args, **kwargs) + except: + return None + self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in lines]) + + + def mark(self, code): + """置換対象になる単語にマーキングを施す + + 対象文字列が 'sort' だとすれば、文字列中にある全ての 'sort' を + '{ランダムな文字列}sort{ランダムな文字列}' + という文字列に置換する。 + """ + if len(self._qs) == 0: + return code + + # 置換対象になる単語を正規表現で表す + def get_target_re(target): + return '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format( + target=re.escape(target) + ) + target_re_text = '|'.join('(?:{})'.format(get_target_re(q.target)) for q in self._qs) + + # 対象となる単語を置換し、その置換された文字列を後で辿るための正規表現(text_re_list)と、 + # 置換された文字列に対してどのような修飾を行えばいいかという辞書(match_qualifier)を作る。 + text_re_list = [] + match_qualifier = { } + def mark_command(match): + # 各置換毎に一意な文字列を用意する + match_name = _make_random_string() + # 対象となる単語がどの修飾のデータなのかを調べる + text = match.group(0) + q = (q for q in self._qs if q.target == text).next() + match_qualifier[match_name] = q + + # text をこの文字列に置換する + text = '{match_name}{original}{match_name}'.format( + match_name=match_name, + original=text, + ) + # 置換された text だけを確実に検索するための正規表現 + text_re = '(?:{match_name}(?P<{match_name}>.*?)(?:{match_name}))'.format( + match_name=match_name + ) + text_re_list.append(text_re) + return text + # 対象になる単語を一括置換 + code = re.sub(target_re_text, mark_command, code) + # マークされた文字列を見つけるための正規表現を作る + self._code_re = re.compile('|'.join(r for r in text_re_list)) + self._match_qualifier = match_qualifier + return code + + def qualify(self, html): + # 修飾の指定がなかった + if len(self._qs) == 0: + return html + # 修飾の指定はあったが、検索してみると修飾する文字列が見つからなかった + if len(self._code_re.pattern) == 0: + return html + + # マークされた文字列を探しだして、そのマークに対応した修飾を行う + def convert(match): + m,q = ((m,q) for m,q in self._match_qualifier.iteritems() if match.group(m)).next() + text = match.group(m) + for command in q.commands: + xs = command.split(' ') + c = xs[0] + remain = xs[1:] + # 修飾 + text = self._qdic.qualify_dic[c](text, *remain) + return text + return self._code_re.sub(convert, html) + +class QualifiedFencedBlockPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + + self.checked_for_codehilite = False + self.codehilite_conf = {} + + def run(self, lines): + # Check for code hilite extension + if not self.checked_for_codehilite: + for ext in self.markdown.registeredExtensions: + if isinstance(ext, CodeHiliteExtension): + self.codehilite_conf = ext.config + break + + self.checked_for_codehilite = True + + text = "\n".join(lines) + while 1: + m = QUALIFIED_FENCED_BLOCK_RE.search(text) + if m: + qualifies = m.group('qualifies') or '' + qualifies = filter(None, qualifies.split('\n')) + code = m.group('code') + qualifier_list = QualifierList(qualifies) + code = qualifier_list.mark(code) + + # If config is not empty, then the codehighlite extension + # is enabled, so we call it to highlite the code + if self.codehilite_conf and m.group('lang'): + highliter = CodeHilite(code, + linenums=self.codehilite_conf['linenums'][0], + guess_lang=self.codehilite_conf['guess_lang'][0], + css_class=self.codehilite_conf['css_class'][0], + style=self.codehilite_conf['pygments_style'][0], + lang=(m.group('lang') or None), + noclasses=self.codehilite_conf['noclasses'][0]) + + code = highliter.hilite() + else: + lang = '' + if m.group('lang'): + lang = LANG_TAG % m.group('lang') + + code = CODE_WRAP % (lang, self._escape(code)) + + code = qualifier_list.qualify(code) + + placeholder = self.markdown.htmlStash.store(code, safe=True) + text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():]) + else: + break + return text.split("\n") + + def _escape(self, txt): + """ basic html escaping """ + txt = txt.replace('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + + +def makeExtension(configs=None): + return QualifiedFencedCodeExtension(configs=configs) From ab0fb5a8e347241f87e1b84f16851f3b1e6718dd Mon Sep 17 00:00:00 2001 From: melpon Date: Sun, 28 Sep 2014 23:48:07 +0900 Subject: [PATCH 02/72] add a config parameter. --- html_attribute.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/html_attribute.py b/html_attribute.py index 3861397..24b26da 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -173,9 +173,12 @@ def _add_border_table(self, element): def _remove_md(self, url): # サイト内絶対パスで末尾に .md があった場合、取り除く # (github のプレビューとの互換性のため) + # その後、指定があればその拡張子を追加する matched = re.match('([^#]*)\.md(#.*)?$', url) if matched: url = matched.group(1) + if self.config['extension']: + url = url + self.config['extension'] anchor = matched.group(2) if anchor is not None: url = url + anchor @@ -246,6 +249,7 @@ def __init__(self, configs): 'base_url' : [None, "Base URL used to link URL as absolute URL"], 'base_path' : [None, "Base Path used to link URL as relative URL"], 'full_path' : [None, "Full Path used to link URL as anchor URL"], + 'extension' : ['', "URL extension"], } # ユーザ設定で上書き From 962f72249152c0423ee3fd403f5b0d2bd90b87bd Mon Sep 17 00:00:00 2001 From: melpon Date: Thu, 30 Oct 2014 02:22:45 +0900 Subject: [PATCH 03/72] output partial html if ParseError occured. --- html_attribute.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/html_attribute.py b/html_attribute.py index 24b26da..33d1380 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -222,7 +222,14 @@ def _to_absolute_url(self, element): def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) - root = etree.fromstring(text.encode('utf-8')) + try: + root = etree.fromstring(text.encode('utf-8')) + except etree.ParseError, e: + lineno = e.position[0] + xs = text.split('\n')[lineno - 5:lineno + 5] + for x, n in zip(xs, range(lineno - 5, lineno + 5)): + print u'{0:5d} {1}'.format(n + 1, x) + raise self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) self._iterate(root, self._to_absolute_url) From 99dd1182adba653d96f66de7edb21125e2fd0422 Mon Sep 17 00:00:00 2001 From: melpon Date: Tue, 18 Nov 2014 00:07:43 +0900 Subject: [PATCH 04/72] fix pep8 warnings. --- footer.py | 7 ++++++- html_attribute.py | 23 +++++++++++++-------- qualified_fenced_code.py | 44 ++++++++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/footer.py b/footer.py index 2b6a0e1..ba7c176 100644 --- a/footer.py +++ b/footer.py @@ -2,13 +2,15 @@ import markdown from markdown.util import etree + class FooterExtension(markdown.Extension): + """ Footer Extension. """ def __init__(self, configs): # デフォルトの設定 self.config = { - 'url' : [None, 'URL'], + 'url': [None, 'URL'], } # ユーザ設定で上書き @@ -21,7 +23,9 @@ def extendMarkdown(self, md, md_globals): md.registerExtension(self) md.treeprocessors.add('footer', footer, '_begin') + class FooterTreeprocessor(markdown.treeprocessors.Treeprocessor): + """ Build and append footnote div to end of document. """ def _make_footer(self): @@ -35,5 +39,6 @@ def run(self, root): footer = self._make_footer() root.append(footer) + def makeExtension(configs=[]): return FooterExtension(configs=configs) diff --git a/html_attribute.py b/html_attribute.py index 33d1380..2caf2aa 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -120,7 +120,9 @@ 'menu', } + class SafeRawHtmlPostprocessor(postprocessors.Postprocessor): + def run(self, text): for i in range(self.markdown.htmlStash.html_counter): html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] @@ -147,7 +149,9 @@ def basic_escape(self, html): html = html.replace('>', '>') return html.replace('"', '"') + class AttributePostprocessor(postprocessors.Postprocessor): + def __init__(self, md): postprocessors.Postprocessor.__init__(self, md) self._markdown = md @@ -185,7 +189,7 @@ def _remove_md(self, url): return url def _to_absolute_url(self, element): - if element.tag == 'a' and element.attrib.has_key('href'): + if element.tag == 'a' and 'href' in element.attrib: base_url = self.config['base_url'].strip('/') base_paths = self.config['base_path'].strip('/').split('/') full_path = self.config['full_path'] @@ -237,11 +241,11 @@ def run(self, text): output = self._markdown.serializer(root) if self._markdown.stripTopLevelTags: try: - start = output.index('<%s>'%self._markdown.doc_tag)+len(self._markdown.doc_tag)+2 - end = output.rindex(''%self._markdown.doc_tag) + start = output.index('<%s>' % self._markdown.doc_tag) + len(self._markdown.doc_tag) + 2 + end = output.rindex('' % self._markdown.doc_tag) output = output[start:end].strip() except ValueError: - if output.strip().endswith('<%s />'%self._markdown.doc_tag): + if output.strip().endswith('<%s />' % self._markdown.doc_tag): # We have an empty document output = '' else: @@ -249,14 +253,16 @@ def run(self, text): raise ValueError('Markdown failed to strip top-level tags. Document=%r' % output.strip()) return output + class AttributeExtension(markdown.Extension): + def __init__(self, configs): # デフォルトの設定 self.config = { - 'base_url' : [None, "Base URL used to link URL as absolute URL"], - 'base_path' : [None, "Base Path used to link URL as relative URL"], - 'full_path' : [None, "Full Path used to link URL as anchor URL"], - 'extension' : ['', "URL extension"], + 'base_url': [None, "Base URL used to link URL as absolute URL"], + 'base_path': [None, "Base Path used to link URL as relative URL"], + 'full_path': [None, "Full Path used to link URL as anchor URL"], + 'extension': ['', "URL extension"], } # ユーザ設定で上書き @@ -269,5 +275,6 @@ def extendMarkdown(self, md, md_globals): md.postprocessors.add('html_attribute', attr, '_end') md.postprocessors['raw_html'] = SafeRawHtmlPostprocessor(md) + def makeExtension(configs): return AttributeExtension(configs) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 20cbad3..0798412 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -34,18 +34,18 @@ import re from markdown.extensions import Extension from markdown.preprocessors import Preprocessor -from markdown.util import Processor from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(\n|(?P.*?\n\s*\n))', re.MULTILINE|re.DOTALL) +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(\n|(?P.*?\n\s*\n))', re.MULTILINE | re.DOTALL) QUALIFY_RE = re.compile(r'^\* +(?P.*?)(?P(\[.*?\])*)$') QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') class QualifiedFencedCodeExtension(Extension): + def extendMarkdown(self, md, md_globals): fenced_block = QualifiedFencedBlockPreprocessor(md) md.registerExtension(self) @@ -61,16 +61,18 @@ def _make_random_string(): alphabets = string.ascii_letters return ''.join(alphabets[randrange(len(alphabets))] for i in xrange(32)) + class QualifyDictionary(object): + def __init__(self): # 各コマンドに対する実際の処理 def _qualify_italic(*xs): return '{0}'.format(*xs) + def _qualify_color(*xs): return '{0}'.format(*xs) + def _qualify_link(*xs): - text = xs[0] - url = xs[1] return '{0}'.format(*xs) self.qualify_dic = { @@ -79,27 +81,33 @@ def _qualify_link(*xs): 'link': _qualify_link, } + class Qualifier(object): + """修飾1個分のデータを保持するクラス """ + def __init__(self, line, qdic): command_res = [r'(\[{cmd}(\]|.*?\]))'.format(cmd=cmd) for cmd in qdic.qualify_dic] qualify_re_str = r'^\* +(?P.*?)(?P({commands})+)$'.format( - commands='|'.join(command_res)) + commands='|'.join(command_res)) qualify_re = re.compile(qualify_re_str) # parsing m = qualify_re.search(line) if not m: - raise ValueError, 'Failed parse' + raise ValueError('Failed parse') self.target = m.group('target') self.commands = [] + def f(match): self.commands.append(match.group(1)) QUALIFY_COMMAND_RE.sub(f, m.group('commands')) + class QualifierList(object): + def __init__(self, lines): self._qdic = QualifyDictionary() @@ -111,7 +119,6 @@ def ignore(f, *args, **kwargs): return None self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in lines]) - def mark(self, code): """置換対象になる単語にマーキングを施す @@ -132,7 +139,8 @@ def get_target_re(target): # 対象となる単語を置換し、その置換された文字列を後で辿るための正規表現(text_re_list)と、 # 置換された文字列に対してどのような修飾を行えばいいかという辞書(match_qualifier)を作る。 text_re_list = [] - match_qualifier = { } + match_qualifier = {} + def mark_command(match): # 各置換毎に一意な文字列を用意する match_name = _make_random_string() @@ -169,7 +177,7 @@ def qualify(self, html): # マークされた文字列を探しだして、そのマークに対応した修飾を行う def convert(match): - m,q = ((m,q) for m,q in self._match_qualifier.iteritems() if match.group(m)).next() + m, q = ((m, q) for m, q in self._match_qualifier.iteritems() if match.group(m)).next() text = match.group(m) for command in q.commands: xs = command.split(' ') @@ -180,6 +188,7 @@ def convert(match): return text return self._code_re.sub(convert, html) + class QualifiedFencedBlockPreprocessor(Preprocessor): def __init__(self, md): @@ -211,13 +220,14 @@ def run(self, lines): # If config is not empty, then the codehighlite extension # is enabled, so we call it to highlite the code if self.codehilite_conf and m.group('lang'): - highliter = CodeHilite(code, - linenums=self.codehilite_conf['linenums'][0], - guess_lang=self.codehilite_conf['guess_lang'][0], - css_class=self.codehilite_conf['css_class'][0], - style=self.codehilite_conf['pygments_style'][0], - lang=(m.group('lang') or None), - noclasses=self.codehilite_conf['noclasses'][0]) + highliter = CodeHilite( + code, + linenums=self.codehilite_conf['linenums'][0], + guess_lang=self.codehilite_conf['guess_lang'][0], + css_class=self.codehilite_conf['css_class'][0], + style=self.codehilite_conf['pygments_style'][0], + lang=(m.group('lang') or None), + noclasses=self.codehilite_conf['noclasses'][0]) code = highliter.hilite() else: @@ -230,7 +240,7 @@ def run(self, lines): code = qualifier_list.qualify(code) placeholder = self.markdown.htmlStash.store(code, safe=True) - text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():]) + text = '%s\n%s\n%s' % (text[:m.start()], placeholder, text[m.end():]) else: break return text.split("\n") From 438f867ef2a31b7435380a2b38067b92e0341bb0 Mon Sep 17 00:00:00 2001 From: melpon Date: Sat, 22 Nov 2014 02:04:21 +0900 Subject: [PATCH 05/72] =?UTF-8?q?code=20=E3=81=AE=E5=A4=89=E6=8F=9B?= =?UTF-8?q?=E3=82=92=E8=A1=8C=E3=82=8F=E3=81=AA=E3=81=84=EF=BC=88CSS=20?= =?UTF-8?q?=E3=81=8C=E4=BD=BF=E3=81=88=E3=82=8B=E3=81=AE=E3=81=A7=E3=82=82?= =?UTF-8?q?=E3=81=86=E4=B8=8D=E8=A6=81=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_attribute.py b/html_attribute.py index 2caf2aa..aa39fba 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -234,7 +234,7 @@ def run(self, text): for x, n in zip(xs, range(lineno - 5, lineno + 5)): print u'{0:5d} {1}'.format(n + 1, x) raise - self._iterate(root, self._add_color_code) + #self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) self._iterate(root, self._to_absolute_url) From 7a9f999a7eb54491e11ef014df5ded2473d582a8 Mon Sep 17 00:00:00 2001 From: Mitsuru Kariya Date: Sat, 10 Jan 2015 18:23:04 +0900 Subject: [PATCH 06/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=83=96?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E3=81=AE=E5=BE=8C=E3=81=AE=E6=94=B9?= =?UTF-8?q?=E8=A1=8C=E3=81=8C=E6=B6=88=E5=A4=B1=E3=81=97=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 0798412..1bfa941 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -39,7 +39,7 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(\n|(?P.*?\n\s*\n))', re.MULTILINE | re.DOTALL) +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) QUALIFY_RE = re.compile(r'^\* +(?P.*?)(?P(\[.*?\])*)$') QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') From 1e18d6e6f5936e6aa3c3cd91702a59a13d5516a0 Mon Sep 17 00:00:00 2001 From: melpon Date: Thu, 15 Jan 2015 23:25:30 +0900 Subject: [PATCH 07/72] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87?= =?UTF-8?q?=E3=82=8C=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/html_attribute.py b/html_attribute.py index aa39fba..006a2e7 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -5,6 +5,7 @@ """ import re +import sys from markdown.util import etree from markdown import postprocessors import markdown @@ -194,6 +195,8 @@ def _to_absolute_url(self, element): base_paths = self.config['base_path'].strip('/').split('/') full_path = self.config['full_path'] + check_href = None + url = element.attrib['href'] if url.startswith('http://') or url.startswith('https://'): # 絶対パス @@ -202,13 +205,17 @@ def _to_absolute_url(self, element): # 別ドメインの場合は別タブで開く if not url_body.startswith(base_url_body): element.attrib['target'] = '_blank' + else: + check_href = url_body[len(base_url_body):] elif url.startswith('/'): # サイト内絶対パス element.attrib['href'] = base_url + url element.attrib['href'] = self._remove_md(element.attrib['href']) + check_href = self._remove_md(url) elif url.startswith('#'): # ページ内リンク element.attrib['href'] = base_url + '/' + self._remove_md(full_path) + url + check_href = '/' + self._remove_md(full_path) else: # サイト内相対パス paths = [] @@ -223,6 +230,31 @@ def _to_absolute_url(self, element): paths.append(p) element.attrib['href'] = base_url + '/' + '/'.join(paths) element.attrib['href'] = self._remove_md(element.attrib['href']) + check_href = self._remove_md('/' + '/'.join(paths)) + + if (hasattr(self._markdown, '_html_attribute_hrefs') and + self._markdown._html_attribute_hrefs is not None): + # パスの存在チェック + if check_href is not None: + check_href = re.sub('#.*', '', check_href) + if check_href.endswith('.nolink'): + # そのうち作られるはずだけど、まだリンク先のファイルが存在していないケース + if self._remove_md(check_href.replace('.nolink', '')) in self._markdown._html_attribute_hrefs: + # .nolink マークされていたけど、実際はもうこのファイルは作られているっぽいケース + # .nolink を外すこと + sys.stderr.write('Warning: [nolinked {full_path}] href "{url} ({check_href})" found.\n'.format(**locals())) + element.tag = 'span' + else: + # このファイルを作るように促す + check_href = check_href.replace('.nolink', '') + sys.stdout.write('Note: You can create {check_href} for {full_path}.\n'.format(**locals())) + element.tag = 'span' + else: + # .nolink でない、普通のファイル + if check_href not in self._markdown._html_attribute_hrefs: + sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals())) + element.tag = 'span' + def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) From 1b307ebe1afb79cf4177fccba5f51a94c42e7f26 Mon Sep 17 00:00:00 2001 From: melpon Date: Fri, 16 Jan 2015 23:30:59 +0900 Subject: [PATCH 08/72] =?UTF-8?q?mailto:=E3=82=92=E7=84=A1=E8=A6=96?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index 006a2e7..3d624ec 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -216,6 +216,9 @@ def _to_absolute_url(self, element): # ページ内リンク element.attrib['href'] = base_url + '/' + self._remove_md(full_path) + url check_href = '/' + self._remove_md(full_path) + elif url.startswith('mailto:'): + # メール + pass else: # サイト内相対パス paths = [] @@ -233,7 +236,7 @@ def _to_absolute_url(self, element): check_href = self._remove_md('/' + '/'.join(paths)) if (hasattr(self._markdown, '_html_attribute_hrefs') and - self._markdown._html_attribute_hrefs is not None): + self._markdown._html_attribute_hrefs is not None): # パスの存在チェック if check_href is not None: check_href = re.sub('#.*', '', check_href) @@ -242,20 +245,19 @@ def _to_absolute_url(self, element): if self._remove_md(check_href.replace('.nolink', '')) in self._markdown._html_attribute_hrefs: # .nolink マークされていたけど、実際はもうこのファイルは作られているっぽいケース # .nolink を外すこと - sys.stderr.write('Warning: [nolinked {full_path}] href "{url} ({check_href})" found.\n'.format(**locals())) + sys.stderr.write('Warning: [nolinked {full_path}] href "{url} ({check_href})" found.\n'.format(**locals()).encode('utf-8')) element.tag = 'span' else: # このファイルを作るように促す check_href = check_href.replace('.nolink', '') - sys.stdout.write('Note: You can create {check_href} for {full_path}.\n'.format(**locals())) + sys.stdout.write('Note: You can create {check_href} for {full_path}.\n'.format(**locals()).encode('utf-8')) element.tag = 'span' else: # .nolink でない、普通のファイル if check_href not in self._markdown._html_attribute_hrefs: - sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals())) + sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals()).encode('utf-8')) element.tag = 'span' - def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) try: From 808424cd6d257da2b11840d87af1ab643de112e9 Mon Sep 17 00:00:00 2001 From: melpon Date: Thu, 19 Feb 2015 22:16:43 +0900 Subject: [PATCH 09/72] add schema.org meta --- html_attribute.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/html_attribute.py b/html_attribute.py index 3d624ec..ddd4172 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -258,6 +258,16 @@ def _to_absolute_url(self, element): sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals()).encode('utf-8')) element.tag = 'span' + def _add_meta(self, element): + body = etree.Element('div', itemprop="articleBody") + for e in list(element): + if e.tag == 'h1': + e.attrib['itemprop'] = 'name' + else: + body.append(e) + element.remove(e) + element.append(body) + def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) try: @@ -271,6 +281,7 @@ def run(self, text): #self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) self._iterate(root, self._to_absolute_url) + self._add_meta(root) output = self._markdown.serializer(root) if self._markdown.stripTopLevelTags: From 903b9c379494c45b8f855ae8fe28ea223a25a2a2 Mon Sep 17 00:00:00 2001 From: melpon Date: Fri, 27 Feb 2015 22:09:41 +0900 Subject: [PATCH 10/72] add meta.py --- meta.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 meta.py diff --git a/meta.py b/meta.py new file mode 100644 index 0000000..4d8e38b --- /dev/null +++ b/meta.py @@ -0,0 +1,64 @@ +#coding: utf-8 +from __future__ import unicode_literals +""" +メタデータ +========================================= + +メタデータを記述できるようにする + + >>> text = '''# push_back + ... * vector[meta header] + ... * function[meta id-type] + ... * std[meta namespace] + ... * vector[meta class] + ... + ... 本文 + ... ''' + >>> md = markdown.Markdown(['meta']) + >>> print md.convert(text) +

コードをここに書く

+

本文

+ >>> md._meta_result + {'header': 'vector', 'id-type': 'function', 'namespace': 'std', 'class': 'vector'} +""" + +from __future__ import absolute_import +import re +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + + +META_RE = re.compile(r'^\s*\*\s*(?P.*?)\[meta\s+(?P.*?)\]\s*$') + + +class MetaExtension(Extension): + + def extendMarkdown(self, md, md_globals): + meta = MetaPreprocessor(md) + md.registerExtension(self) + + md.preprocessors.add('meta', meta, ">normalize_whitespace") + + +class MetaPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + self._markdown = md + + def run(self, lines): + new_lines = [] + self._markdown._meta_result = {} + for line in lines: + m = META_RE.match(line) + if m: + target = m.group('target') + name = m.group('name') + self._markdown._meta_result[name] = target + else: + new_lines.append(line) + return new_lines + + +def makeExtension(configs=None): + return MetaExtension(configs=configs) From f5128411fe80c569ee416d49597c6bb048ff0523 Mon Sep 17 00:00:00 2001 From: melpon Date: Wed, 4 Mar 2015 00:48:25 +0900 Subject: [PATCH 11/72] =?UTF-8?q?=E3=83=A1=E3=82=BF=E6=83=85=E5=A0=B1?= =?UTF-8?q?=E3=82=92html=E3=81=AB=E5=8F=8D=E6=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 4 +++- meta.py | 32 +++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index ddd4172..e3458b5 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -260,10 +260,12 @@ def _to_absolute_url(self, element): def _add_meta(self, element): body = etree.Element('div', itemprop="articleBody") + after_h1 = False for e in list(element): if e.tag == 'h1': e.attrib['itemprop'] = 'name' - else: + after_h1 = True + elif after_h1: body.append(e) element.remove(e) element.append(body) diff --git a/meta.py b/meta.py index 4d8e38b..77c1941 100644 --- a/meta.py +++ b/meta.py @@ -26,6 +26,7 @@ import re from markdown.extensions import Extension from markdown.preprocessors import Preprocessor +from markdown import postprocessors META_RE = re.compile(r'^\s*\*\s*(?P.*?)\[meta\s+(?P.*?)\]\s*$') @@ -34,10 +35,12 @@ class MetaExtension(Extension): def extendMarkdown(self, md, md_globals): - meta = MetaPreprocessor(md) - md.registerExtension(self) + metapre = MetaPreprocessor(md) + metapost = MetaPostprocessor(md) - md.preprocessors.add('meta', meta, ">normalize_whitespace") + md.registerExtension(self) + md.preprocessors.add('meta', metapre, ">normalize_whitespace") + md.postprocessors.add('meta', metapost, '_end') class MetaPreprocessor(Preprocessor): @@ -60,5 +63,28 @@ def run(self, lines): return new_lines +class MetaPostprocessor(postprocessors.Postprocessor): + + def __init__(self, md): + postprocessors.Postprocessor.__init__(self, md) + self._markdown = md + + def run(self, text): + if not hasattr(self._markdown, '_meta_result'): + return text + + meta = self._markdown._meta_result + + if 'class' in meta: + text = text.replace('

', '

{cls}::'.format(cls=meta['class'])) + if 'namespace' in meta: + text = text.replace('

', '

{ns}::'.format(ns=meta['namespace'])) + if 'header' in meta: + text = '
<{}>
'.format(meta['header']) + text + if 'id-type' in meta: + text = '
{}
'.format(meta['id-type']) + text + return text + + def makeExtension(configs=None): return MetaExtension(configs=configs) From 7a4331c30bbcddb79c8ec7e457a15cea4dcaea90 Mon Sep 17 00:00:00 2001 From: melpon Date: Wed, 11 Mar 2015 22:11:34 +0900 Subject: [PATCH 12/72] add mathjax extension --- mathjax.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 mathjax.py diff --git a/mathjax.py b/mathjax.py new file mode 100644 index 0000000..788d0a1 --- /dev/null +++ b/mathjax.py @@ -0,0 +1,79 @@ +#coding: utf-8 +""" +MathJax を使えるようにする +========================= + +テキストのどこかに以下の文字列を書くことで有効になる + +* [mathjax enable] + +MathJaxを有効にした場合、$$...$$ (ブロック)か $...$ (インライン)に挟まれた文字列が数式になる +""" + +from __future__ import unicode_literals +from __future__ import absolute_import +import re +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + + +MATHJAX_CONFIG_RE = re.compile(r'^\s*\*\s*(?P.*?)\[mathjax\s+(?P.*?)\]\s*$') +MATHJAX_BLOCK_RE = re.compile(r'\$\$.*?\$\$', re.MULTILINE | re.DOTALL) +MATHJAX_INLINE_RE = re.compile(r'\$[^\\\$]*(?:\\\$[^\\\$]*)*\$') + + +class MathJaxExtension(Extension): + + def extendMarkdown(self, md, md_globals): + mathjaxpre = MathJaxPreprocessor(md) + + md.registerExtension(self) + md.preprocessors.add('mathjax', mathjaxpre, ">normalize_whitespace") + + +class MathJaxPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + self._markdown = md + + def run(self, lines): + lines2 = [] + self._markdown._mathjax_enabled = False + for line in lines: + m = MATHJAX_CONFIG_RE.match(line) + if m: + name = m.group('name') + if name == 'enable': + self._markdown._mathjax_enabled = True + else: + lines2.append(line) + if not self._markdown._mathjax_enabled: + return lines2 + + text = "\n".join(lines2) + while True: + m = MATHJAX_BLOCK_RE.search(text) + if not m: + break + tex = m.group(0) + placeholder = self.markdown.htmlStash.store(tex, safe=False) + text = text[:m.start()] + placeholder + text[m.end():] + + lines3 = [] + + for line in text.split('\n'): + while True: + m = MATHJAX_INLINE_RE.search(line) + if not m: + break + tex = m.group(0) + placeholder = self.markdown.htmlStash.store(tex, safe=False) + line = line[:m.start()] + placeholder + line[m.end():] + lines3.append(line) + + return lines3 + + +def makeExtension(configs=None): + return MathJaxExtension(configs=configs) From 4b819c4bc634763094a0634145a9417b1934f6cd Mon Sep 17 00:00:00 2001 From: melpon Date: Fri, 25 Sep 2015 17:17:09 +0900 Subject: [PATCH 13/72] implements C++ version meta. (https://github.com/cpprefjp/site/issues/256) --- meta.py | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/meta.py b/meta.py index 77c1941..7ed57be 100644 --- a/meta.py +++ b/meta.py @@ -11,6 +11,8 @@ ... * function[meta id-type] ... * std[meta namespace] ... * vector[meta class] + ... * cpp11deprecated[meta cpp] + ... * cpp14removed[meta cpp] ... ... 本文 ... ''' @@ -19,7 +21,7 @@

コードをここに書く

本文

>>> md._meta_result - {'header': 'vector', 'id-type': 'function', 'namespace': 'std', 'class': 'vector'} + {'header': ['vector'], 'id-type': ['function'], 'namespace': ['std'], 'class': ['vector'], 'cpp': ['cpp11deprecated', 'cpp14removed']} """ from __future__ import absolute_import @@ -57,7 +59,9 @@ def run(self, lines): if m: target = m.group('target') name = m.group('name') - self._markdown._meta_result[name] = target + if name not in self._markdown._meta_result: + self._markdown._meta_result[name] = [] + self._markdown._meta_result[name].append(target) else: new_lines.append(line) return new_lines @@ -69,20 +73,51 @@ def __init__(self, md): postprocessors.Postprocessor.__init__(self, md) self._markdown = md + CPP_DIC = { + 'cpp11': { + 'class_name': 'cpp11', + 'title': 'C++11で追加', + 'text': '(C++11)', + }, + 'cpp14': { + 'class_name': 'cpp14', + 'title': 'C++14で追加', + 'text': '(C++14)', + }, + 'cpp11deprecated': { + 'class_name': 'cpp11deprecated text-warning', + 'title': 'C++11で非推奨', + 'text': '(C++11で非推奨)', + }, + 'cpp14deprecated': { + 'class_name': 'cpp14deprecated text-warning', + 'title': 'C++14で非推奨', + 'text': '(C++14で非推奨)', + }, + 'cpp14removed': { + 'class_name': 'cpp14removed text-danger', + 'title': 'C++11で削除', + 'text': '(C++11で削除)', + }, + } + def run(self, text): if not hasattr(self._markdown, '_meta_result'): return text meta = self._markdown._meta_result + if 'cpp' in meta: + for name in meta['cpp']: + text = text.replace('

', '{text}'.format(**self.CPP_DIC[name])) if 'class' in meta: - text = text.replace('

', '

{cls}::'.format(cls=meta['class'])) + text = text.replace('

', '

{cls}::'.format(cls=meta['class'][0])) if 'namespace' in meta: - text = text.replace('

', '

{ns}::'.format(ns=meta['namespace'])) + text = text.replace('

', '

{ns}::'.format(ns=meta['namespace'][0])) if 'header' in meta: - text = '
<{}>
'.format(meta['header']) + text + text = '
<{}>
'.format(meta['header'][0]) + text if 'id-type' in meta: - text = '
{}
'.format(meta['id-type']) + text + text = '
{}
'.format(meta['id-type'][0]) + text return text From c7aaba7c93766eb047982d3924c988ecd12882a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Wed, 4 Nov 2015 19:16:22 +0900 Subject: [PATCH 14/72] fix converting error --- qualified_fenced_code.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 1bfa941..d1f7b66 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -123,7 +123,7 @@ def mark(self, code): """置換対象になる単語にマーキングを施す 対象文字列が 'sort' だとすれば、文字列中にある全ての 'sort' を - '{ランダムな文字列}sort{ランダムな文字列}' + '{ランダムな文字列}' という文字列に置換する。 """ if len(self._qs) == 0: @@ -150,12 +150,11 @@ def mark_command(match): match_qualifier[match_name] = q # text をこの文字列に置換する - text = '{match_name}{original}{match_name}'.format( + text = '{match_name}'.format( match_name=match_name, - original=text, ) # 置換された text だけを確実に検索するための正規表現 - text_re = '(?:{match_name}(?P<{match_name}>.*?)(?:{match_name}))'.format( + text_re = '(?P<{match_name}>{match_name})'.format( match_name=match_name ) text_re_list.append(text_re) @@ -178,7 +177,7 @@ def qualify(self, html): # マークされた文字列を探しだして、そのマークに対応した修飾を行う def convert(match): m, q = ((m, q) for m, q in self._match_qualifier.iteritems() if match.group(m)).next() - text = match.group(m) + text = q.target for command in q.commands: xs = command.split(' ') c = xs[0] From 80641407831aa2e66b36c28d3a228e45552a35c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Thu, 5 Nov 2015 12:46:31 +0900 Subject: [PATCH 15/72] escape qualifing targets. --- qualified_fenced_code.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index d1f7b66..d8b58fd 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -62,6 +62,15 @@ def _make_random_string(): return ''.join(alphabets[randrange(len(alphabets))] for i in xrange(32)) +def _escape(txt): + """ basic html escaping """ + txt = txt.replace('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + + class QualifyDictionary(object): def __init__(self): @@ -177,7 +186,7 @@ def qualify(self, html): # マークされた文字列を探しだして、そのマークに対応した修飾を行う def convert(match): m, q = ((m, q) for m, q in self._match_qualifier.iteritems() if match.group(m)).next() - text = q.target + text = _escape(q.target) for command in q.commands: xs = command.split(' ') c = xs[0] @@ -234,7 +243,7 @@ def run(self, lines): if m.group('lang'): lang = LANG_TAG % m.group('lang') - code = CODE_WRAP % (lang, self._escape(code)) + code = CODE_WRAP % (lang, _escape(code)) code = qualifier_list.qualify(code) @@ -244,14 +253,6 @@ def run(self, lines): break return text.split("\n") - def _escape(self, txt): - """ basic html escaping """ - txt = txt.replace('&', '&') - txt = txt.replace('<', '<') - txt = txt.replace('>', '>') - txt = txt.replace('"', '"') - return txt - def makeExtension(configs=None): return QualifiedFencedCodeExtension(configs=configs) From d98bb3660a2068d4c8f23c22b918cb08b8f0fa8c Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Fri, 25 Dec 2015 16:32:35 +0900 Subject: [PATCH 16/72] =?UTF-8?q?=E5=A4=89=E6=8F=9B=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=97=E3=81=9F=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=81=AB=E3=80=81=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=90=8D=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 1 + 1 file changed, 1 insertion(+) diff --git a/html_attribute.py b/html_attribute.py index e3458b5..efcadfb 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -277,6 +277,7 @@ def run(self, text): except etree.ParseError, e: lineno = e.position[0] xs = text.split('\n')[lineno - 5:lineno + 5] + print u'[Parse Error : {0}]'.format(self.config['full_path']) for x, n in zip(xs, range(lineno - 5, lineno + 5)): print u'{0:5d} {1}'.format(n + 1, x) raise From 25855ff9c1e6bf0db470a1b6dc21585e202a8d2d Mon Sep 17 00:00:00 2001 From: melpon Date: Fri, 11 Mar 2016 10:17:28 +0900 Subject: [PATCH 17/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E8=A6=8F=E7=B4=84=E3=81=AB=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E5=90=88=E3=82=8F=E3=81=9B=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- footer.py | 15 ++++++++------- html_attribute.py | 20 ++++++++++++-------- mathjax.py | 9 ++++++--- meta.py | 11 +++++++---- qualified_fenced_code.py | 25 ++++++++++++++----------- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/footer.py b/footer.py index ba7c176..1300614 100644 --- a/footer.py +++ b/footer.py @@ -1,12 +1,15 @@ -#coding: utf-8 +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + import markdown from markdown.util import etree class FooterExtension(markdown.Extension): - - """ Footer Extension. """ - + """Footer Extension.""" def __init__(self, configs): # デフォルトの設定 self.config = { @@ -25,9 +28,7 @@ def extendMarkdown(self, md, md_globals): class FooterTreeprocessor(markdown.treeprocessors.Treeprocessor): - - """ Build and append footnote div to end of document. """ - + """Build and append footnote div to end of document.""" def _make_footer(self): footer = etree.Element('footer') a = etree.SubElement(footer, 'a') diff --git a/html_attribute.py b/html_attribute.py index efcadfb..4ab02fa 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -1,14 +1,18 @@ -#coding: utf-8 -from __future__ import unicode_literals +# -*- coding: utf-8 -*- """ markdown から変換した HTML に属性を追加する """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals import re import sys -from markdown.util import etree -from markdown import postprocessors + import markdown +from markdown import postprocessors +from markdown.util import etree HTML_TAGS = { 'html', @@ -274,14 +278,14 @@ def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) try: root = etree.fromstring(text.encode('utf-8')) - except etree.ParseError, e: + except etree.ParseError as e: lineno = e.position[0] xs = text.split('\n')[lineno - 5:lineno + 5] - print u'[Parse Error : {0}]'.format(self.config['full_path']) + print('[Parse Error : {0}]'.format(self.config['full_path']).encode('utf-8')) for x, n in zip(xs, range(lineno - 5, lineno + 5)): - print u'{0:5d} {1}'.format(n + 1, x) + print('{0:5d} {1}'.format(n + 1, x).encode('utf-8')) raise - #self._iterate(root, self._add_color_code) + # self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) self._iterate(root, self._to_absolute_url) self._add_meta(root) diff --git a/mathjax.py b/mathjax.py index 788d0a1..4f8a32c 100644 --- a/mathjax.py +++ b/mathjax.py @@ -1,4 +1,4 @@ -#coding: utf-8 +# -*- coding: utf-8 -*- """ MathJax を使えるようにする ========================= @@ -9,10 +9,13 @@ MathJaxを有効にした場合、$$...$$ (ブロック)か $...$ (インライン)に挟まれた文字列が数式になる """ - -from __future__ import unicode_literals from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + import re + from markdown.extensions import Extension from markdown.preprocessors import Preprocessor diff --git a/meta.py b/meta.py index 7ed57be..d6c5dcb 100644 --- a/meta.py +++ b/meta.py @@ -1,5 +1,4 @@ -#coding: utf-8 -from __future__ import unicode_literals +# -*- coding: utf-8 -*- """ メタデータ ========================================= @@ -23,12 +22,16 @@ >>> md._meta_result {'header': ['vector'], 'id-type': ['function'], 'namespace': ['std'], 'class': ['vector'], 'cpp': ['cpp11deprecated', 'cpp14removed']} """ - from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + import re + from markdown.extensions import Extension -from markdown.preprocessors import Preprocessor from markdown import postprocessors +from markdown.preprocessors import Preprocessor META_RE = re.compile(r'^\s*\*\s*(?P.*?)\[meta\s+(?P.*?)\]\s*$') diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index d8b58fd..b28e428 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -1,5 +1,4 @@ -#coding: utf-8 -from __future__ import unicode_literals +# -*- coding: utf-8 -*- """ Fenced Code Extension の改造版 ========================================= @@ -29,12 +28,18 @@ ... ''' >>> print markdown.markdown(text, extensions=['qualified_fenced_code']) """ - from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + import re + +from markdown.extensions.codehilite import CodeHilite +from markdown.extensions.codehilite import CodeHiliteExtension from markdown.extensions import Extension from markdown.preprocessors import Preprocessor -from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension + CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' @@ -54,16 +59,15 @@ def extendMarkdown(self, md, md_globals): def _make_random_string(): - """アルファベットから成るランダムな文字列を作る - """ - import string + """アルファベットから成るランダムな文字列を作る""" from random import randrange + import string alphabets = string.ascii_letters return ''.join(alphabets[randrange(len(alphabets))] for i in xrange(32)) def _escape(txt): - """ basic html escaping """ + """basic html escaping""" txt = txt.replace('&', '&') txt = txt.replace('<', '<') txt = txt.replace('>', '>') @@ -93,8 +97,7 @@ def _qualify_link(*xs): class Qualifier(object): - """修飾1個分のデータを保持するクラス - """ + """修飾1個分のデータを保持するクラス""" def __init__(self, line, qdic): command_res = [r'(\[{cmd}(\]|.*?\]))'.format(cmd=cmd) for cmd in qdic.qualify_dic] @@ -124,7 +127,7 @@ def __init__(self, lines): def ignore(f, *args, **kwargs): try: return f(*args, **kwargs) - except: + except Exception: return None self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in lines]) From 584861e47c7fd78029657426fb990190d9587dfe Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 1 Sep 2016 01:39:38 +0900 Subject: [PATCH 18/72] =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=83=88=E5=85=A8?= =?UTF-8?q?=E4=BD=93=E3=81=A7=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E4=BF=AE?= =?UTF-8?q?=E9=A3=BE=E3=83=AA=E3=82=B9=E3=83=88=E3=82=92=E5=A4=96=E3=81=8B?= =?UTF-8?q?=E3=82=89=E5=8F=97=E3=81=91=E5=8F=96=E3=82=8C=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index b28e428..dccc8ba 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -40,6 +40,7 @@ from markdown.extensions import Extension from markdown.preprocessors import Preprocessor +import traceback CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' @@ -51,8 +52,13 @@ class QualifiedFencedCodeExtension(Extension): + + def __init__(self, configs): + global_qualify_list = configs[0][1] + self.global_qualify_list = global_qualify_list + def extendMarkdown(self, md, md_globals): - fenced_block = QualifiedFencedBlockPreprocessor(md) + fenced_block = QualifiedFencedBlockPreprocessor(md, self.global_qualify_list) md.registerExtension(self) md.preprocessors.add('qualified_fenced_code', fenced_block, ">normalize_whitespace") @@ -202,11 +208,12 @@ def convert(match): class QualifiedFencedBlockPreprocessor(Preprocessor): - def __init__(self, md): + def __init__(self, md, global_qualify_list): Preprocessor.__init__(self, md) self.checked_for_codehilite = False self.codehilite_conf = {} + self.global_qualify_list = global_qualify_list def run(self, lines): # Check for code hilite extension @@ -223,6 +230,8 @@ def run(self, lines): m = QUALIFIED_FENCED_BLOCK_RE.search(text) if m: qualifies = m.group('qualifies') or '' + qualifies = qualifies + self.global_qualify_list + print('qualifies : {}'.format(qualifies)) qualifies = filter(None, qualifies.split('\n')) code = m.group('code') qualifier_list = QualifierList(qualifies) From 8a191aa91feac62b97e568876cedde3976bdccc1 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 1 Sep 2016 01:53:52 +0900 Subject: [PATCH 19/72] =?UTF-8?q?=E3=83=87=E3=83=90=E3=83=83=E3=82=B0?= =?UTF-8?q?=E7=94=A8=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index dccc8ba..ca7f783 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -231,7 +231,6 @@ def run(self, lines): if m: qualifies = m.group('qualifies') or '' qualifies = qualifies + self.global_qualify_list - print('qualifies : {}'.format(qualifies)) qualifies = filter(None, qualifies.split('\n')) code = m.group('code') qualifier_list = QualifierList(qualifies) From e64b29167c06b70c60d372401e2afab994247f8c Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Wed, 11 Jan 2017 13:54:09 +0900 Subject: [PATCH 20/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E8=A6=8F=E7=B4=84=E3=81=AE=E8=AD=A6=E5=91=8A?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不要なインポートを削除 クラス定義の改行が多すぎたので、空行は1行だけにした --- qualified_fenced_code.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index ca7f783..4b3524b 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -40,8 +40,6 @@ from markdown.extensions import Extension from markdown.preprocessors import Preprocessor -import traceback - CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' @@ -52,7 +50,6 @@ class QualifiedFencedCodeExtension(Extension): - def __init__(self, configs): global_qualify_list = configs[0][1] self.global_qualify_list = global_qualify_list From f4efcba16e0831850cbf5831917bab30ce30eece Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Wed, 11 Jan 2017 15:07:24 +0900 Subject: [PATCH 21/72] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=9F=E3=82=B3=E3=83=BC=E3=83=89=E4=BF=AE=E9=A3=BE=E3=81=AE?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E3=81=8C=E3=82=B3=E3=83=BC=E3=83=89=E4=B8=AD?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88=E3=81=AB=E3=80=81?= =?UTF-8?q?=E7=84=A1=E8=A6=96=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 4b3524b..92e5842 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -132,7 +132,17 @@ def ignore(f, *args, **kwargs): return f(*args, **kwargs) except Exception: return None - self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in lines]) + + def unique(xs): + seen = set() + results = [] + for x in xs: + if x not in seen: + seen.add(x) + results.append(x) + return results + + self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in unique(lines)]) def mark(self, code): """置換対象になる単語にマーキングを施す @@ -142,6 +152,7 @@ def mark(self, code): という文字列に置換する。 """ if len(self._qs) == 0: + self._code_re = re.compile("") return code # 置換対象になる単語を正規表現で表す @@ -149,7 +160,17 @@ def get_target_re(target): return '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format( target=re.escape(target) ) - target_re_text = '|'.join('(?:{})'.format(get_target_re(q.target)) for q in self._qs) + def find_match(target): + pattern = re.compile(target) + return pattern.search(code) != None + + pre_target_re_text_list = ['(?:{})'.format(get_target_re(q.target)) for q in self._qs] + pre_target_re_text_list = filter(find_match, pre_target_re_text_list) + if len(pre_target_re_text_list) == 0: + self._code_re = re.compile("") + return code + + target_re_text = '|'.join(pre_target_re_text_list) # 対象となる単語を置換し、その置換された文字列を後で辿るための正規表現(text_re_list)と、 # 置換された文字列に対してどのような修飾を行えばいいかという辞書(match_qualifier)を作る。 From e8bdbadd6e2838c8bd1ce377316a915cc6bcad08 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Fri, 24 Mar 2017 16:52:41 +0900 Subject: [PATCH 22/72] =?UTF-8?q?C++17=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=83=A1=E3=82=BF=E6=83=85=E5=A0=B1=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/cpprefjp/site/issues/397 --- meta.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/meta.py b/meta.py index d6c5dcb..4e4089f 100644 --- a/meta.py +++ b/meta.py @@ -87,6 +87,11 @@ def __init__(self, md): 'title': 'C++14で追加', 'text': '(C++14)', }, + 'cpp17': { + 'class_name': 'cpp17', + 'title': 'C++17で追加', + 'text': '(C++17)', + }, 'cpp11deprecated': { 'class_name': 'cpp11deprecated text-warning', 'title': 'C++11で非推奨', @@ -102,6 +107,16 @@ def __init__(self, md): 'title': 'C++11で削除', 'text': '(C++11で削除)', }, + 'cpp17deprecated': { + 'class_name': 'cpp17deprecated text-warning', + 'title': 'C++17で非推奨', + 'text': '(C++17で非推奨)', + }, + 'cpp17removed': { + 'class_name': 'cpp17removed text-danger', + 'title': 'C++17で削除', + 'text': '(C++17で削除)', + }, } def run(self, text): From 1dd3c81f8a90adf563d2b3a43bbf618d7aba6fea Mon Sep 17 00:00:00 2001 From: Mitsuru Kariya Date: Sat, 25 Mar 2017 18:10:20 +0900 Subject: [PATCH 23/72] =?UTF-8?q?fenced=20code=20block=20=E3=81=AE?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fenced code block 全体がインデントされていた場合に、インデントを削除するようにした。 タブのインデント量は、4としている。 なお、fenced code block 全体がインデントされているかどうかの判断は、 閉じマーク(終わりの```)がインデントされているかどうかで判断している。 また、削除するインデント量も閉じマークのインデント量としている。 したがって、閉じマークがインデントされていなければ、従来の挙動と変わらない。 --- qualified_fenced_code.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 92e5842..e0eeab3 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -43,9 +43,10 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)\s*(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) QUALIFY_RE = re.compile(r'^\* +(?P.*?)(?P(\[.*?\])*)$') QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') +INDENT_RE = re.compile(r'^[ \t]+', re.MULTILINE) class QualifiedFencedCodeExtension(Extension): @@ -224,6 +225,13 @@ def convert(match): return self._code_re.sub(convert, html) +def _removeIndent(code, indent): + if len(indent) == 0: + return code + l = len(indent.expandtabs(4)) + return INDENT_RE.sub(lambda m: m.group().expandtabs(4)[l:], code) + + class QualifiedFencedBlockPreprocessor(Preprocessor): def __init__(self, md, global_qualify_list): @@ -250,7 +258,7 @@ def run(self, lines): qualifies = m.group('qualifies') or '' qualifies = qualifies + self.global_qualify_list qualifies = filter(None, qualifies.split('\n')) - code = m.group('code') + code = _removeIndent(*m.group('code', 'indent')) qualifier_list = QualifierList(qualifies) code = qualifier_list.mark(code) From 97c9871659ce76d15130ca8da3449965b98d41bb Mon Sep 17 00:00:00 2001 From: Mitsuru Kariya Date: Tue, 28 Mar 2017 18:42:56 +0900 Subject: [PATCH 24/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E4=BF=AE?= =?UTF-8?q?=E9=A3=BE=E3=81=AB=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=8C=E4=BB=98=E3=81=84=E3=81=A6=E3=81=84=E3=81=A6=E3=82=82?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E3=81=AB=E6=A9=9F=E8=83=BD=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index e0eeab3..5df1c94 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -44,7 +44,6 @@ LANG_TAG = ' class="%s"' QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) -QUALIFY_RE = re.compile(r'^\* +(?P.*?)(?P(\[.*?\])*)$') QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') INDENT_RE = re.compile(r'^[ \t]+', re.MULTILINE) @@ -106,7 +105,7 @@ class Qualifier(object): def __init__(self, line, qdic): command_res = [r'(\[{cmd}(\]|.*?\]))'.format(cmd=cmd) for cmd in qdic.qualify_dic] - qualify_re_str = r'^\* +(?P.*?)(?P({commands})+)$'.format( + qualify_re_str = r'^[ \t]*\*[ \t]+(?P.*?)(?P({commands})+)$'.format( commands='|'.join(command_res)) qualify_re = re.compile(qualify_re_str) @@ -161,9 +160,10 @@ def get_target_re(target): return '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format( target=re.escape(target) ) + def find_match(target): pattern = re.compile(target) - return pattern.search(code) != None + return pattern.search(code) is not None pre_target_re_text_list = ['(?:{})'.format(get_target_re(q.target)) for q in self._qs] pre_target_re_text_list = filter(find_match, pre_target_re_text_list) From fb18c87b48c6290dd6ba00141ecb2f5dc8aba930 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Mon, 24 Apr 2017 17:31:32 +0900 Subject: [PATCH 25/72] =?UTF-8?q?Python=E6=A8=99=E6=BA=96=E3=81=AE?= =?UTF-8?q?=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BEre=E3=81=8C=E3=82=B0?= =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=97=E6=95=B0100=E5=80=8B=E3=81=AE?= =?UTF-8?q?=E5=88=B6=E9=99=90=E3=81=8C=E3=81=82=E3=82=8B=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=80=81500=E5=80=8B=E5=88=B6=E9=99=90=E3=81=AEregex=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA=E3=81=AB=E5=88=87=E3=82=8A?= =?UTF-8?q?=E6=9B=BF=E3=81=88=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 5df1c94..fbe7174 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -33,7 +33,7 @@ from __future__ import print_function from __future__ import unicode_literals -import re +import regex as re from markdown.extensions.codehilite import CodeHilite from markdown.extensions.codehilite import CodeHiliteExtension From f7115f020b5da9601d3e5f4b56498105b852d8d1 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Mon, 24 Apr 2017 17:55:40 +0900 Subject: [PATCH 26/72] =?UTF-8?q?=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88?= =?UTF-8?q?=E5=BE=8C=E3=81=AE=E5=A4=89=E6=8F=9B=E3=81=A7=E3=81=99=E3=81=90?= =?UTF-8?q?=E3=81=ABregex=E3=83=A9=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA?= =?UTF-8?q?=E3=81=8C=E4=BD=BF=E3=82=8F=E3=82=8C=E3=80=81pip=20install=20re?= =?UTF-8?q?gex=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84?= =?UTF-8?q?=E7=8A=B6=E6=B3=81=E3=81=A7=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AB=E3=81=AA=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=81=9F=E3=82=81=E3=80=81=E4=B8=80?= =?UTF-8?q?=E6=97=A6=E6=88=BB=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index fbe7174..5df1c94 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -33,7 +33,7 @@ from __future__ import print_function from __future__ import unicode_literals -import regex as re +import re from markdown.extensions.codehilite import CodeHilite from markdown.extensions.codehilite import CodeHiliteExtension From a5b0e0155cbcc36ef106988d1a0e348c017ad62f Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Wed, 26 Apr 2017 17:14:04 +0900 Subject: [PATCH 27/72] =?UTF-8?q?=E3=81=9D=E3=82=8D=E3=81=9D=E3=82=8D?= =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=81=ABregex=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA=E3=81=8C=E5=85=A5=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E9=A0=83=E3=81=A0=E3=81=A8=E6=80=9D?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=81=A7=E3=80=81=20https://github.com/cppre?= =?UTF-8?q?fjp/markdown=5Fto=5Fhtml/commit/fb18c87b48c6290dd6ba00141ecb2f5?= =?UTF-8?q?dc8aba930=20=E3=82=92=E5=86=8D=E3=82=B3=E3=83=9F=E3=83=83?= =?UTF-8?q?=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 5df1c94..fbe7174 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -33,7 +33,7 @@ from __future__ import print_function from __future__ import unicode_literals -import re +import regex as re from markdown.extensions.codehilite import CodeHilite from markdown.extensions.codehilite import CodeHiliteExtension From e2d8733779c502289fd02a3866310edd7250133c Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 27 Apr 2017 15:51:59 +0900 Subject: [PATCH 28/72] =?UTF-8?q?=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BE?= =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=82=92re=E3=81=8B?= =?UTF-8?q?=E3=82=89regex=E3=81=AB=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88?= =?UTF-8?q?=E3=81=9F=E3=81=93=E3=81=A8=E3=81=A7=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E4=BF=AE=E9=A3=BE=E3=81=8C=E8=A1=8C=E3=82=8F=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=9F=E5=95=8F=E9=A1=8C=E3=81=B8?= =?UTF-8?q?=E3=81=AE=E5=AF=BE=E5=87=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/cpprefjp/site_generator/issues/16 https://gist.github.com/faithandbrave/e8c67e2b2ae0bb5c58aa6f46ad9ff79b --- qualified_fenced_code.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index fbe7174..0e84e5b 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -118,7 +118,12 @@ def __init__(self, line, qdic): def f(match): self.commands.append(match.group(1)) - QUALIFY_COMMAND_RE.sub(f, m.group('commands')) + + try: + QUALIFY_COMMAND_RE.sub(f, m.group('commands')) + except TypeError: + pass # workaround for regex library + # TypeError: expected string instance, NoneType found class QualifierList(object): From 49d449eeb732d1ccb61dcff125c474ba5fd103f2 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Wed, 23 Aug 2017 14:56:47 +0900 Subject: [PATCH 29/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E111 indentation is not a multiple of four E113 unexpected indentation --- qualified_fenced_code.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 0e84e5b..cf4bd32 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -122,8 +122,9 @@ def f(match): try: QUALIFY_COMMAND_RE.sub(f, m.group('commands')) except TypeError: - pass # workaround for regex library - # TypeError: expected string instance, NoneType found + # workaround for regex library + # TypeError: expected string instance, NoneType found + pass class QualifierList(object): From 2344bb558e87c4eea1a0fbbbad18ddf7d1b9b3a2 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Sat, 2 Sep 2017 15:53:18 +0900 Subject: [PATCH 30/72] =?UTF-8?q?C++20=E3=81=AE=E3=82=BF=E3=82=B0=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/meta.py b/meta.py index 4e4089f..48ada0d 100644 --- a/meta.py +++ b/meta.py @@ -92,6 +92,11 @@ def __init__(self, md): 'title': 'C++17で追加', 'text': '(C++17)', }, + 'cpp20': { + 'class_name': 'cpp20', + 'title': 'C++20で追加', + 'text': '(C++20)', + }, 'cpp11deprecated': { 'class_name': 'cpp11deprecated text-warning', 'title': 'C++11で非推奨', @@ -117,6 +122,16 @@ def __init__(self, md): 'title': 'C++17で削除', 'text': '(C++17で削除)', }, + 'cpp20deprecated': { + 'class_name': 'cpp20deprecated text-warning', + 'title': 'C++20で非推奨', + 'text': '(C++20で非推奨)', + }, + 'cpp20removed': { + 'class_name': 'cpp20removed text-danger', + 'title': 'C++20で削除', + 'text': '(C++20で削除)', + }, } def run(self, text): From 151798b7d1efd9e06294f4c014ba7cf1026500ff Mon Sep 17 00:00:00 2001 From: Nana Sakisaka Date: Thu, 2 Nov 2017 11:41:07 +0900 Subject: [PATCH 31/72] =?UTF-8?q?=E3=83=98=E3=83=87=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=81=AE=E5=AD=90=E8=A6=81=E7=B4=A0=E3=82=92=E5=BF=85?= =?UTF-8?q?=E3=81=9Aspan=E3=81=A7=E3=83=A9=E3=83=83=E3=83=97=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meta.py b/meta.py index 48ada0d..d3a98b8 100644 --- a/meta.py +++ b/meta.py @@ -140,6 +140,8 @@ def run(self, text): meta = self._markdown._meta_result + text = text.replace('

', '

').replace('

', '

') + if 'cpp' in meta: for name in meta['cpp']: text = text.replace('', '{text}'.format(**self.CPP_DIC[name])) From 4e9f3812b046ebd26d7cd7712a5dbe79b0a354c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Mon, 13 Nov 2017 20:56:43 +0900 Subject: [PATCH 32/72] =?UTF-8?q?Qualifier=20=E3=82=92=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=81=99=E3=82=8B=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=81=A7=E5=B0=91=E3=81=97=E9=AB=98=E9=80=9F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index cf4bd32..3593b17 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -126,6 +126,22 @@ def f(match): # TypeError: expected string instance, NoneType found pass + self._target_re = None + + # 置換対象になる単語を正規表現で表す + def get_target_re(self): + if self._target_re is None: + self._target_re = '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format(target=re.escape(self.target)) + self._target_re + + _cache = {} + + @classmethod + def get(cls, line, qdic): + if line not in cls._cache: + cls._cache[line] = cls(line, qdic) + cls._cache[line] + class QualifierList(object): @@ -133,9 +149,9 @@ def __init__(self, lines): self._qdic = QualifyDictionary() # Qualifier を作るが、エラーになったデータは取り除く - def ignore(f, *args, **kwargs): + def ignore(cls, *args, **kwargs): try: - return f(*args, **kwargs) + return cls.get(*args, **kwargs) except Exception: return None @@ -161,17 +177,11 @@ def mark(self, code): self._code_re = re.compile("") return code - # 置換対象になる単語を正規表現で表す - def get_target_re(target): - return '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format( - target=re.escape(target) - ) - def find_match(target): pattern = re.compile(target) return pattern.search(code) is not None - pre_target_re_text_list = ['(?:{})'.format(get_target_re(q.target)) for q in self._qs] + pre_target_re_text_list = ['(?:{})'.format(q.get_target_re()) for q in self._qs] pre_target_re_text_list = filter(find_match, pre_target_re_text_list) if len(pre_target_re_text_list) == 0: self._code_re = re.compile("") From adb2493241cb0010253f78728de53dc5e34134d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Tue, 14 Nov 2017 09:21:48 +0900 Subject: [PATCH 33/72] =?UTF-8?q?=E3=81=84=E3=82=84=E6=9C=80=E8=BF=91=20El?= =?UTF-8?q?ixir=20=E3=81=B0=E3=81=A3=E3=81=8B=E3=82=8A=E8=A7=A6=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=9F=E3=81=8B=E3=82=89=E2=80=A6=E2=80=A6=E3=81=AD?= =?UTF-8?q?=EF=BC=9F=20(https://github.com/cpprefjp/site=5Fgenerator/issue?= =?UTF-8?q?s/51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 3593b17..cf6f0c2 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -132,7 +132,7 @@ def f(match): def get_target_re(self): if self._target_re is None: self._target_re = '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format(target=re.escape(self.target)) - self._target_re + return self._target_re _cache = {} @@ -140,7 +140,7 @@ def get_target_re(self): def get(cls, line, qdic): if line not in cls._cache: cls._cache[line] = cls(line, qdic) - cls._cache[line] + return cls._cache[line] class QualifierList(object): From cde76e6efbc2d9a3763f54b7dfc9eb73099ead7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Tue, 14 Nov 2017 09:43:43 +0900 Subject: [PATCH 34/72] =?UTF-8?q?find=5Fmatch=20=E3=81=A7=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=83=91=E3=82=A4=E3=83=AB=E3=81=97=E3=81=A6=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E7=84=A1=E3=81=8F=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index cf6f0c2..526ab1c 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -127,13 +127,24 @@ def f(match): pass self._target_re = None + self._target_re_text = None # 置換対象になる単語を正規表現で表す - def get_target_re(self): + def get_target_re_text(self): + if self._target_re_text is None: + target_re_text = '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format(target=re.escape(self.target)) + self._target_re_text = '(?:{})'.format(target_re_text) + return self._target_re_text + + def _get_target_re(self): if self._target_re is None: - self._target_re = '((?<=[^a-zA-Z_])|(?:^)){target}((?=[^a-zA-Z_])|(?:$))'.format(target=re.escape(self.target)) + target_re = re.compile(self.get_target_re_text()) + self._target_re = target_re return self._target_re + def find_match(self, code): + return self._get_target_re().search(code) is not None + _cache = {} @classmethod @@ -177,12 +188,7 @@ def mark(self, code): self._code_re = re.compile("") return code - def find_match(target): - pattern = re.compile(target) - return pattern.search(code) is not None - - pre_target_re_text_list = ['(?:{})'.format(q.get_target_re()) for q in self._qs] - pre_target_re_text_list = filter(find_match, pre_target_re_text_list) + pre_target_re_text_list = [q.get_target_re_text() for q in self._qs if q.find_match(code)] if len(pre_target_re_text_list) == 0: self._code_re = re.compile("") return code From 71f4010c3b440a7f488b1765c6667fdc623845e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Tue, 28 Nov 2017 09:50:15 +0900 Subject: [PATCH 35/72] =?UTF-8?q?"```cpp=20example"=20=E3=81=A8=E3=81=84?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=81=8C=E3=82=B3=E3=83=BC=E3=83=89=E3=83=96?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E3=81=A8=E3=81=97=E3=81=A6=E8=AA=8D?= =?UTF-8?q?=E8=AD=98=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 526ab1c..cda640f 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -43,7 +43,7 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)[ ]*\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*).*?\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') INDENT_RE = re.compile(r'^[ \t]+', re.MULTILINE) From 101e1b92d1e328bc93d5473df75292d877d499de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Tue, 28 Nov 2017 23:01:07 +0900 Subject: [PATCH 36/72] =?UTF-8?q?Markdown=202.6.9=20=E3=81=AE=20tables=20?= =?UTF-8?q?=E6=8B=A1=E5=BC=B5=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=E6=8C=81=E3=81=A3=E3=81=A6=E3=81=8D=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tables.py | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 tables.py diff --git a/tables.py b/tables.py new file mode 100644 index 0000000..e0f5bf5 --- /dev/null +++ b/tables.py @@ -0,0 +1,228 @@ +""" +Tables Extension for Python-Markdown +==================================== + +Added parsing of tables to Python-Markdown. + +See +for documentation. + +Original code Copyright 2009 [Waylan Limberg](http://achinghead.com) + +All changes Copyright 2008-2014 The Python Markdown Project + +License: [BSD](http://www.opensource.org/licenses/bsd-license.php) + +現在使っている Markdown 2.3.1 のバージョンが古すぎてボロボロだったので、 +Markdown 2.6.9 の tables 拡張のコードを持ってきた。 +""" + +from __future__ import absolute_import +from __future__ import unicode_literals +from markdown.extensions import Extension +from markdown.blockprocessors import BlockProcessor +from markdown.util import etree +import re +PIPE_NONE = 0 +PIPE_LEFT = 1 +PIPE_RIGHT = 2 + + +class TableProcessor(BlockProcessor): + """ Process Tables. """ + + RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))') + RE_END_BORDER = re.compile(r'(? 1: + header0 = rows[0] + self.border = PIPE_NONE + if header0.startswith('|'): + self.border |= PIPE_LEFT + if self.RE_END_BORDER.search(header0) is not None: + self.border |= PIPE_RIGHT + row = self._split_row(header0) + row0_len = len(row) + is_table = row0_len > 1 + + # Each row in a single column table needs at least one pipe. + if not is_table and row0_len == 1 and self.border: + for index in range(1, len(rows)): + is_table = rows[index].startswith('|') + if not is_table: + is_table = self.RE_END_BORDER.search(rows[index]) is not None + if not is_table: + break + + if is_table: + row = self._split_row(rows[1]) + is_table = (len(row) == row0_len) and set(''.join(row)) <= set('|:- ') + if is_table: + self.separator = row + + return is_table + + def run(self, parent, blocks): + """ Parse a table block and build table. """ + block = blocks.pop(0).split('\n') + header = block[0].strip() + rows = [] if len(block) < 3 else block[2:] + + # Get alignment of columns + align = [] + for c in self.separator: + c = c.strip() + if c.startswith(':') and c.endswith(':'): + align.append('center') + elif c.startswith(':'): + align.append('left') + elif c.endswith(':'): + align.append('right') + else: + align.append(None) + + # Build table + table = etree.SubElement(parent, 'table') + thead = etree.SubElement(table, 'thead') + self._build_row(header, thead, align) + tbody = etree.SubElement(table, 'tbody') + if len(rows) == 0: + # Handle empty table + self._build_empty_row(tbody, align) + else: + for row in rows: + self._build_row(row.strip(), tbody, align) + + def _build_empty_row(self, parent, align): + """Build an empty row.""" + tr = etree.SubElement(parent, 'tr') + count = len(align) + while count: + etree.SubElement(tr, 'td') + count -= 1 + + def _build_row(self, row, parent, align): + """ Given a row of text, build table cells. """ + tr = etree.SubElement(parent, 'tr') + tag = 'td' + if parent.tag == 'thead': + tag = 'th' + cells = self._split_row(row) + # We use align here rather than cells to ensure every row + # contains the same number of columns. + for i, a in enumerate(align): + c = etree.SubElement(tr, tag) + try: + c.text = cells[i].strip() + except IndexError: # pragma: no cover + c.text = "" + if a: + c.set('align', a) + + def _split_row(self, row): + """ split a row of text into list of cells. """ + if self.border: + if row.startswith('|'): + row = row[1:] + row = self.RE_END_BORDER.sub('', row) + return self._split(row) + + def _split(self, row): + """ split a row of text with some code into a list of cells. """ + elements = [] + pipes = [] + tics = [] + tic_points = [] + tic_region = [] + good_pipes = [] + + # Parse row + # Throw out \\, and \| + for m in self.RE_CODE_PIPES.finditer(row): + # Store ` data (len, start_pos, end_pos) + if m.group(2): + # \`+ + # Store length of each tic group: subtract \ + tics.append(len(m.group(2)) - 1) + # Store start of group, end of group, and escape length + tic_points.append((m.start(2), m.end(2) - 1, 1)) + elif m.group(3): + # `+ + # Store length of each tic group + tics.append(len(m.group(3))) + # Store start of group, end of group, and escape length + tic_points.append((m.start(3), m.end(3) - 1, 0)) + # Store pipe location + elif m.group(5): + pipes.append(m.start(5)) + + # Pair up tics according to size if possible + # Subtract the escape length *only* from the opening. + # Walk through tic list and see if tic has a close. + # Store the tic region (start of region, end of region). + pos = 0 + tic_len = len(tics) + while pos < tic_len: + try: + tic_size = tics[pos] - tic_points[pos][2] + if tic_size == 0: + raise ValueError + index = tics[pos + 1:].index(tic_size) + 1 + tic_region.append((tic_points[pos][0], tic_points[pos + index][1])) + pos += index + 1 + except ValueError: + pos += 1 + + # Resolve pipes. Check if they are within a tic pair region. + # Walk through pipes comparing them to each region. + # - If pipe position is less that a region, it isn't in a region + # - If it is within a region, we don't want it, so throw it out + # - If we didn't throw it out, it must be a table pipe + for pipe in pipes: + throw_out = False + for region in tic_region: + if pipe < region[0]: + # Pipe is not in a region + break + elif region[0] <= pipe <= region[1]: + # Pipe is within a code region. Throw it out. + throw_out = True + break + if not throw_out: + good_pipes.append(pipe) + + # Split row according to table delimeters. + pos = 0 + for pipe in good_pipes: + elements.append(row[pos:pipe]) + pos = pipe + 1 + elements.append(row[pos:]) + return elements + + +class TableExtension(Extension): + """ Add tables to Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Add an instance of TableProcessor to BlockParser. """ + if '|' not in md.ESCAPED_CHARS: + md.ESCAPED_CHARS.append('|') + md.parser.blockprocessors.add('table', + TableProcessor(md.parser), + ' Date: Tue, 28 Nov 2017 23:14:09 +0900 Subject: [PATCH 37/72] add encoding --- tables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tables.py b/tables.py index e0f5bf5..1e17816 100644 --- a/tables.py +++ b/tables.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Tables Extension for Python-Markdown ==================================== From a567e5b66ba5523724f671a87d1ffcad550dd44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Tue, 5 Dec 2017 00:27:30 +0900 Subject: [PATCH 38/72] =?UTF-8?q?=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E6=8A=BD=E5=87=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 4 +++- qualified_fenced_code.py | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index 4ab02fa..d4f3034 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -128,6 +128,8 @@ class SafeRawHtmlPostprocessor(postprocessors.Postprocessor): + HTML_TAG_RE = re.compile(r'^\<\/?([a-zA-Z0-9]+)[^\>]*\>$') + def run(self, text): for i in range(self.markdown.htmlStash.html_counter): html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] @@ -138,7 +140,7 @@ def run(self, text): def escape(self, html): # html tag - m = re.match(r'^\<\/?([a-zA-Z0-9]+)[^\>]*\>$', html) + m = re.match(self.HTML_TAG_RE, html) if m: if m.group(1) in HTML_TAGS: return html diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index cda640f..fc417e2 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -33,6 +33,8 @@ from __future__ import print_function from __future__ import unicode_literals +import hashlib + import regex as re from markdown.extensions.codehilite import CodeHilite @@ -43,7 +45,7 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*).*?\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)(?P.*?)\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') INDENT_RE = re.compile(r'^[ \t]+', re.MULTILINE) @@ -259,6 +261,7 @@ class QualifiedFencedBlockPreprocessor(Preprocessor): def __init__(self, md, global_qualify_list): Preprocessor.__init__(self, md) + md._example_codes = [] self.checked_for_codehilite = False self.codehilite_conf = {} self.global_qualify_list = global_qualify_list @@ -277,10 +280,19 @@ def run(self, lines): while 1: m = QUALIFIED_FENCED_BLOCK_RE.search(text) if m: + # ```cpp example みたいに書かれていたらサンプルコードとして扱う + is_example = m.group('lang_meta') and ('example' in m.group('lang_meta').strip().split()) + qualifies = m.group('qualifies') or '' qualifies = qualifies + self.global_qualify_list qualifies = filter(None, qualifies.split('\n')) code = _removeIndent(*m.group('code', 'indent')) + + # サンプルコードだったら、self.markdown の中にコードの情報と ID を入れておく + if is_example: + example_id = hashlib.sha1(code.encode('utf-8')).hexdigest() + self.markdown._example_codes.append({"id": example_id, "code": code}) + qualifier_list = QualifierList(qualifies) code = qualifier_list.mark(code) @@ -297,6 +309,9 @@ def run(self, lines): noclasses=self.codehilite_conf['noclasses'][0]) code = highliter.hilite() + # サンプルコードだったら
で囲む + if is_example: + code = '
%s
' % (example_id, code) else: lang = '' if m.group('lang'): From 4db0b67ab2cfa581bcdf18ec7d763044c8772c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Wed, 27 Dec 2017 21:26:53 +0900 Subject: [PATCH 39/72] =?UTF-8?q?.yata=20=E3=82=92=E4=B8=80=E6=99=82?= =?UTF-8?q?=E7=9A=84=E3=81=AB=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index fc417e2..85e4aae 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -311,7 +311,8 @@ def run(self, lines): code = highliter.hilite() # サンプルコードだったら
で囲む if is_example: - code = '
%s
' % (example_id, code) + # おかしくなったので今は .yata 消しておく + code = '
%s
' % (example_id, code) else: lang = '' if m.group('lang'): From 3679fdd2f56507d3faccb9358b2d0ead9c90e3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Wed, 27 Dec 2017 21:35:54 +0900 Subject: [PATCH 40/72] =?UTF-8?q?=E3=82=BF=E3=82=B0=E3=81=94=E3=81=A8?= =?UTF-8?q?=E6=B6=88=E3=81=95=E3=81=AA=E3=81=84=E3=81=A8=E3=83=80=E3=83=A1?= =?UTF-8?q?=E3=81=A0=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 85e4aae..c3fd538 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -311,8 +311,9 @@ def run(self, lines): code = highliter.hilite() # サンプルコードだったら
で囲む if is_example: - # おかしくなったので今は .yata 消しておく - code = '
%s
' % (example_id, code) + # ちょっとおかしくなったので何もしない + #code = '
%s
' % (example_id, code) + pass else: lang = '' if m.group('lang'): From b144758bb0c0ffc7d67741929f4802b6fabf3c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Wed, 27 Dec 2017 22:10:27 +0900 Subject: [PATCH 41/72] =?UTF-8?q?=E5=A4=9A=E5=88=86=E7=9B=B4=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=81=A7=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index c3fd538..fc417e2 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -311,9 +311,7 @@ def run(self, lines): code = highliter.hilite() # サンプルコードだったら
で囲む if is_example: - # ちょっとおかしくなったので何もしない - #code = '
%s
' % (example_id, code) - pass + code = '
%s
' % (example_id, code) else: lang = '' if m.group('lang'): From 899e2523da700c4509d35e24134e5ff7d8f2ee1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Thu, 28 Dec 2017 09:59:38 +0900 Subject: [PATCH 42/72] =?UTF-8?q?=E3=82=BF=E3=82=B0=E4=B8=80=E8=A6=A7?= =?UTF-8?q?=E3=82=92=E3=82=BD=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 178 +++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index d4f3034..087088b 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -15,114 +15,114 @@ from markdown.util import etree HTML_TAGS = { - 'html', - 'head', - 'title', - 'base', - 'link', - 'meta', - 'style', - 'script', - 'noscript', - 'body', - 'section', - 'nav', + 'a', + 'abbr', + 'address', + 'area', 'article', 'aside', + 'audio', + 'b', + 'base', + 'bdi', + 'bdo', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'cite', + 'code', + 'col', + 'colgroup', + 'command', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'div', + 'dl', + 'dt', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', - 'hgroup', + 'head', 'header', - 'footer', - 'address', - 'p', + 'hgroup', 'hr', - 'pre', - 'blockquote', - 'ol', - 'ul', - 'li', - 'dl', - 'dt', - 'dd', - 'figure', - 'figcaption', - 'div', - 'a', - 'em', - 'strong', - 'small', - 's', - 'cite', - 'q', - 'dfn', - 'abbr', - 'time', - 'code', - 'var', - 'samp', - 'kbd', - 'sub', - 'sup', + 'html', 'i', - 'b', - 'u', - 'mark', - 'ruby', - 'rt', - 'rp', - 'bdi', - 'bdo', - 'span', - 'br', - 'wbr', - 'ins', - 'del', - 'img', 'iframe', - 'embed', + 'img', + 'input', + 'ins', + 'kbd', + 'keygen', + 'label', + 'legend', + 'li', + 'link', + 'map', + 'mark', + 'menu', + 'meta', + 'meter', + 'nav', + 'noscript', 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', 'param', - 'video', - 'audio', + 'pre', + 'progress', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'small', 'source', - 'track', - 'canvas', - 'map', - 'area', + 'span', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', 'table', - 'caption', - 'colgroup', - 'col', 'tbody', - 'thead', - 'tfoot', - 'tr', 'td', - 'th', - 'form', - 'fieldset', - 'legend', - 'label', - 'input', - 'button', - 'select', - 'datalist', - 'optgroup', - 'option', 'textarea', - 'keygen', - 'output', - 'progress', - 'meter', - 'details', - 'summary', - 'command', - 'menu', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'u', + 'ul', + 'var', + 'video', + 'wbr', } From d2595fce69937297504e2f1794c6d7843a68e106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E9=82=89=E8=8C=82=E9=9B=85?= Date: Wed, 21 Feb 2018 18:58:02 +0900 Subject: [PATCH 43/72] =?UTF-8?q?ID=20=E3=81=8C=E5=90=8C=E3=81=98=E3=81=AB?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 6 +++++- tables.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index fc417e2..15d4922 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -277,6 +277,9 @@ def run(self, lines): self.checked_for_codehilite = True text = "\n".join(lines) + + example_counter = 0 + while 1: m = QUALIFIED_FENCED_BLOCK_RE.search(text) if m: @@ -290,8 +293,9 @@ def run(self, lines): # サンプルコードだったら、self.markdown の中にコードの情報と ID を入れておく if is_example: - example_id = hashlib.sha1(code.encode('utf-8')).hexdigest() + example_id = hashlib.sha1(str(example_counter) + code.encode('utf-8')).hexdigest() self.markdown._example_codes.append({"id": example_id, "code": code}) + example_counter += 1 qualifier_list = QualifierList(qualifies) code = qualifier_list.mark(code) diff --git a/tables.py b/tables.py index 1e17816..2572bdf 100644 --- a/tables.py +++ b/tables.py @@ -20,10 +20,13 @@ from __future__ import absolute_import from __future__ import unicode_literals + +import re + from markdown.extensions import Extension from markdown.blockprocessors import BlockProcessor from markdown.util import etree -import re + PIPE_NONE = 0 PIPE_LEFT = 1 PIPE_RIGHT = 2 From 1043bb1346d05a3a9d52e7e4c023f5ef86577e46 Mon Sep 17 00:00:00 2001 From: Mitsuru Kariya Date: Sun, 27 Jan 2019 23:57:28 +0900 Subject: [PATCH 44/72] =?UTF-8?q?Python3=20=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- footer.py | 4 ---- html_attribute.py | 16 ++++++--------- mathjax.py | 4 ---- meta.py | 4 ---- qualified_fenced_code.py | 42 +++++++++++++--------------------------- tables.py | 3 --- 6 files changed, 19 insertions(+), 54 deletions(-) diff --git a/footer.py b/footer.py index 1300614..10afc9f 100644 --- a/footer.py +++ b/footer.py @@ -1,8 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import markdown from markdown.util import etree diff --git a/html_attribute.py b/html_attribute.py index 087088b..43b96b1 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -2,10 +2,6 @@ """ markdown から変換した HTML に属性を追加する """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import re import sys @@ -251,17 +247,17 @@ def _to_absolute_url(self, element): if self._remove_md(check_href.replace('.nolink', '')) in self._markdown._html_attribute_hrefs: # .nolink マークされていたけど、実際はもうこのファイルは作られているっぽいケース # .nolink を外すこと - sys.stderr.write('Warning: [nolinked {full_path}] href "{url} ({check_href})" found.\n'.format(**locals()).encode('utf-8')) + sys.stderr.write('Warning: [nolinked {full_path}] href "{url} ({check_href})" found.\n'.format(**locals())) element.tag = 'span' else: # このファイルを作るように促す check_href = check_href.replace('.nolink', '') - sys.stdout.write('Note: You can create {check_href} for {full_path}.\n'.format(**locals()).encode('utf-8')) + sys.stdout.write('Note: You can create {check_href} for {full_path}.\n'.format(**locals())) element.tag = 'span' else: # .nolink でない、普通のファイル if check_href not in self._markdown._html_attribute_hrefs: - sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals()).encode('utf-8')) + sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals())) element.tag = 'span' def _add_meta(self, element): @@ -279,13 +275,13 @@ def _add_meta(self, element): def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) try: - root = etree.fromstring(text.encode('utf-8')) + root = etree.fromstring(text) except etree.ParseError as e: lineno = e.position[0] xs = text.split('\n')[lineno - 5:lineno + 5] - print('[Parse Error : {0}]'.format(self.config['full_path']).encode('utf-8')) + print('[Parse Error : {0}]'.format(self.config['full_path'])) for x, n in zip(xs, range(lineno - 5, lineno + 5)): - print('{0:5d} {1}'.format(n + 1, x).encode('utf-8')) + print('{0:5d} {1}'.format(n + 1, x)) raise # self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) diff --git a/mathjax.py b/mathjax.py index 4f8a32c..07c3b38 100644 --- a/mathjax.py +++ b/mathjax.py @@ -9,10 +9,6 @@ MathJaxを有効にした場合、$$...$$ (ブロック)か $...$ (インライン)に挟まれた文字列が数式になる """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import re diff --git a/meta.py b/meta.py index d3a98b8..2063f5d 100644 --- a/meta.py +++ b/meta.py @@ -22,10 +22,6 @@ >>> md._meta_result {'header': ['vector'], 'id-type': ['function'], 'namespace': ['std'], 'class': ['vector'], 'cpp': ['cpp11deprecated', 'cpp14removed']} """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import re diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 15d4922..ea8635e 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -28,10 +28,6 @@ ... ''' >>> print markdown.markdown(text, extensions=['qualified_fenced_code']) """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import hashlib @@ -53,8 +49,7 @@ class QualifiedFencedCodeExtension(Extension): def __init__(self, configs): - global_qualify_list = configs[0][1] - self.global_qualify_list = global_qualify_list + self.global_qualify_list = configs['global_qualify_list'] def extendMarkdown(self, md, md_globals): fenced_block = QualifiedFencedBlockPreprocessor(md, self.global_qualify_list) @@ -68,7 +63,7 @@ def _make_random_string(): from random import randrange import string alphabets = string.ascii_letters - return ''.join(alphabets[randrange(len(alphabets))] for i in xrange(32)) + return ''.join(alphabets[randrange(len(alphabets))] for i in range(32)) def _escape(txt): @@ -147,14 +142,6 @@ def _get_target_re(self): def find_match(self, code): return self._get_target_re().search(code) is not None - _cache = {} - - @classmethod - def get(cls, line, qdic): - if line not in cls._cache: - cls._cache[line] = cls(line, qdic) - return cls._cache[line] - class QualifierList(object): @@ -162,22 +149,19 @@ def __init__(self, lines): self._qdic = QualifyDictionary() # Qualifier を作るが、エラーになったデータは取り除く - def ignore(cls, *args, **kwargs): - try: - return cls.get(*args, **kwargs) - except Exception: - return None - def unique(xs): seen = set() results = [] for x in xs: if x not in seen: seen.add(x) - results.append(x) + try: + results.append(Qualifier(x, self._qdic)) + except Exception: + pass return results - self._qs = filter(None, [ignore(Qualifier, v, self._qdic) for v in unique(lines)]) + self._qs = unique(lines) def mark(self, code): """置換対象になる単語にマーキングを施す @@ -207,7 +191,7 @@ def mark_command(match): match_name = _make_random_string() # 対象となる単語がどの修飾のデータなのかを調べる text = match.group(0) - q = (q for q in self._qs if q.target == text).next() + q = next(q for q in self._qs if q.target == text) match_qualifier[match_name] = q # text をこの文字列に置換する @@ -237,7 +221,7 @@ def qualify(self, html): # マークされた文字列を探しだして、そのマークに対応した修飾を行う def convert(match): - m, q = ((m, q) for m, q in self._match_qualifier.iteritems() if match.group(m)).next() + q = next(q for m, q in self._match_qualifier.items() if match.group(m)) text = _escape(q.target) for command in q.commands: xs = command.split(' ') @@ -288,12 +272,12 @@ def run(self, lines): qualifies = m.group('qualifies') or '' qualifies = qualifies + self.global_qualify_list - qualifies = filter(None, qualifies.split('\n')) + qualifies = [f for f in qualifies.split('\n') if f] code = _removeIndent(*m.group('code', 'indent')) # サンプルコードだったら、self.markdown の中にコードの情報と ID を入れておく if is_example: - example_id = hashlib.sha1(str(example_counter) + code.encode('utf-8')).hexdigest() + example_id = hashlib.sha1((str(example_counter) + code).encode('utf-8')).hexdigest() self.markdown._example_codes.append({"id": example_id, "code": code}) example_counter += 1 @@ -332,5 +316,5 @@ def run(self, lines): return text.split("\n") -def makeExtension(configs=None): - return QualifiedFencedCodeExtension(configs=configs) +def makeExtension(configs): + return QualifiedFencedCodeExtension(dict(configs)) diff --git a/tables.py b/tables.py index 2572bdf..65f4f73 100644 --- a/tables.py +++ b/tables.py @@ -18,9 +18,6 @@ Markdown 2.6.9 の tables 拡張のコードを持ってきた。 """ -from __future__ import absolute_import -from __future__ import unicode_literals - import re from markdown.extensions import Extension From a798bf5b6b201ffd4411d33377b4ade257e2be3d Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Wed, 24 Jul 2019 13:45:11 +0900 Subject: [PATCH 45/72] =?UTF-8?q?C++23=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/meta.py b/meta.py index d3a98b8..66adcf6 100644 --- a/meta.py +++ b/meta.py @@ -97,6 +97,11 @@ def __init__(self, md): 'title': 'C++20で追加', 'text': '(C++20)', }, + 'cpp23': { + 'class_name': 'cpp23', + 'title': 'C++23で追加', + 'text': '(C++23)', + }, 'cpp11deprecated': { 'class_name': 'cpp11deprecated text-warning', 'title': 'C++11で非推奨', @@ -132,6 +137,16 @@ def __init__(self, md): 'title': 'C++20で削除', 'text': '(C++20で削除)', }, + 'cpp23deprecated': { + 'class_name': 'cpp23deprecated text-warning', + 'title': 'C++23で非推奨', + 'text': '(C++23で非推奨)', + }, + 'cpp23removed': { + 'class_name': 'cpp23removed text-danger', + 'title': 'C++23で削除', + 'text': '(C++23で削除)', + }, } def run(self, text): From f528f80c1d43ba261dcb09bb4946b96b757eb4e7 Mon Sep 17 00:00:00 2001 From: melpon Date: Wed, 4 Mar 2020 15:34:13 +0900 Subject: [PATCH 46/72] =?UTF-8?q?=E6=9C=80=E6=96=B0=E3=81=AEMarkdown?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- footer.py | 4 +- html_attribute.py | 30 +++-- mathjax.py | 9 +- meta.py | 4 +- qualified_fenced_code.py | 14 +-- tables.py | 229 --------------------------------------- 6 files changed, 30 insertions(+), 260 deletions(-) delete mode 100644 tables.py diff --git a/footer.py b/footer.py index 10afc9f..6920132 100644 --- a/footer.py +++ b/footer.py @@ -37,5 +37,5 @@ def run(self, root): root.append(footer) -def makeExtension(configs=[]): - return FooterExtension(configs=configs) +def makeExtension(**kwargs): + return FooterExtension(**kwargs) diff --git a/html_attribute.py b/html_attribute.py index 43b96b1..50e515c 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -8,7 +8,8 @@ import markdown from markdown import postprocessors -from markdown.util import etree + +import xml.etree.ElementTree as etree HTML_TAGS = { 'a', @@ -128,9 +129,9 @@ class SafeRawHtmlPostprocessor(postprocessors.Postprocessor): def run(self, text): for i in range(self.markdown.htmlStash.html_counter): - html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] - if not safe: - html = self.escape(html) + html = self.markdown.htmlStash.rawHtmlBlocks[i] + # if not safe: + # html = self.escape(html) text = text.replace(self.markdown.htmlStash.get_placeholder(i), html) return text @@ -181,7 +182,7 @@ def _remove_md(self, url): # サイト内絶対パスで末尾に .md があった場合、取り除く # (github のプレビューとの互換性のため) # その後、指定があればその拡張子を追加する - matched = re.match('([^#]*)\.md(#.*)?$', url) + matched = re.match('([^#]*)\\.md(#.*)?$', url) if matched: url = matched.group(1) if self.config['extension']: @@ -237,8 +238,7 @@ def _to_absolute_url(self, element): element.attrib['href'] = self._remove_md(element.attrib['href']) check_href = self._remove_md('/' + '/'.join(paths)) - if (hasattr(self._markdown, '_html_attribute_hrefs') and - self._markdown._html_attribute_hrefs is not None): + if hasattr(self._markdown, '_html_attribute_hrefs') and self._markdown._html_attribute_hrefs is not None: # パスの存在チェック if check_href is not None: check_href = re.sub('#.*', '', check_href) @@ -306,18 +306,16 @@ def run(self, text): class AttributeExtension(markdown.Extension): - def __init__(self, configs): + def __init__(self, **kwargs): # デフォルトの設定 self.config = { - 'base_url': [None, "Base URL used to link URL as absolute URL"], - 'base_path': [None, "Base Path used to link URL as relative URL"], - 'full_path': [None, "Full Path used to link URL as anchor URL"], + 'base_url': ['', "Base URL used to link URL as absolute URL"], + 'base_path': ['', "Base Path used to link URL as relative URL"], + 'full_path': ['', "Full Path used to link URL as anchor URL"], 'extension': ['', "URL extension"], } - # ユーザ設定で上書き - for key, value in configs: - self.setConfig(key, value) + super().__init__(**kwargs) def extendMarkdown(self, md, md_globals): attr = AttributePostprocessor(md) @@ -326,5 +324,5 @@ def extendMarkdown(self, md, md_globals): md.postprocessors['raw_html'] = SafeRawHtmlPostprocessor(md) -def makeExtension(configs): - return AttributeExtension(configs) +def makeExtension(**kwargs): + return AttributeExtension(**kwargs) diff --git a/mathjax.py b/mathjax.py index 07c3b38..09af179 100644 --- a/mathjax.py +++ b/mathjax.py @@ -14,6 +14,7 @@ from markdown.extensions import Extension from markdown.preprocessors import Preprocessor +from markdown.util import code_escape MATHJAX_CONFIG_RE = re.compile(r'^\s*\*\s*(?P.*?)\[mathjax\s+(?P.*?)\]\s*$') @@ -56,7 +57,7 @@ def run(self, lines): if not m: break tex = m.group(0) - placeholder = self.markdown.htmlStash.store(tex, safe=False) + placeholder = self.markdown.htmlStash.store(code_escape(tex)) text = text[:m.start()] + placeholder + text[m.end():] lines3 = [] @@ -67,12 +68,12 @@ def run(self, lines): if not m: break tex = m.group(0) - placeholder = self.markdown.htmlStash.store(tex, safe=False) + placeholder = self.markdown.htmlStash.store(code_escape(tex)) line = line[:m.start()] + placeholder + line[m.end():] lines3.append(line) return lines3 -def makeExtension(configs=None): - return MathJaxExtension(configs=configs) +def makeExtension(**kwargs): + return MathJaxExtension(**kwargs) diff --git a/meta.py b/meta.py index 7cde0b6..b06aac3 100644 --- a/meta.py +++ b/meta.py @@ -167,5 +167,5 @@ def run(self, text): return text -def makeExtension(configs=None): - return MetaExtension(configs=configs) +def makeExtension(**kwargs): + return MetaExtension(**kwargs) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index ea8635e..6509299 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -48,8 +48,8 @@ class QualifiedFencedCodeExtension(Extension): - def __init__(self, configs): - self.global_qualify_list = configs['global_qualify_list'] + def __init__(self, global_qualify_list): + self.global_qualify_list = global_qualify_list def extendMarkdown(self, md, md_globals): fenced_block = QualifiedFencedBlockPreprocessor(md, self.global_qualify_list) @@ -236,8 +236,8 @@ def convert(match): def _removeIndent(code, indent): if len(indent) == 0: return code - l = len(indent.expandtabs(4)) - return INDENT_RE.sub(lambda m: m.group().expandtabs(4)[l:], code) + n = len(indent.expandtabs(4)) + return INDENT_RE.sub(lambda m: m.group().expandtabs(4)[n:], code) class QualifiedFencedBlockPreprocessor(Preprocessor): @@ -309,12 +309,12 @@ def run(self, lines): code = qualifier_list.qualify(code) - placeholder = self.markdown.htmlStash.store(code, safe=True) + placeholder = self.markdown.htmlStash.store(code) text = '%s\n%s\n%s' % (text[:m.start()], placeholder, text[m.end():]) else: break return text.split("\n") -def makeExtension(configs): - return QualifiedFencedCodeExtension(dict(configs)) +def makeExtension(**kwargs): + return QualifiedFencedCodeExtension(**kwargs) diff --git a/tables.py b/tables.py deleted file mode 100644 index 65f4f73..0000000 --- a/tables.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tables Extension for Python-Markdown -==================================== - -Added parsing of tables to Python-Markdown. - -See -for documentation. - -Original code Copyright 2009 [Waylan Limberg](http://achinghead.com) - -All changes Copyright 2008-2014 The Python Markdown Project - -License: [BSD](http://www.opensource.org/licenses/bsd-license.php) - -現在使っている Markdown 2.3.1 のバージョンが古すぎてボロボロだったので、 -Markdown 2.6.9 の tables 拡張のコードを持ってきた。 -""" - -import re - -from markdown.extensions import Extension -from markdown.blockprocessors import BlockProcessor -from markdown.util import etree - -PIPE_NONE = 0 -PIPE_LEFT = 1 -PIPE_RIGHT = 2 - - -class TableProcessor(BlockProcessor): - """ Process Tables. """ - - RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))') - RE_END_BORDER = re.compile(r'(? 1: - header0 = rows[0] - self.border = PIPE_NONE - if header0.startswith('|'): - self.border |= PIPE_LEFT - if self.RE_END_BORDER.search(header0) is not None: - self.border |= PIPE_RIGHT - row = self._split_row(header0) - row0_len = len(row) - is_table = row0_len > 1 - - # Each row in a single column table needs at least one pipe. - if not is_table and row0_len == 1 and self.border: - for index in range(1, len(rows)): - is_table = rows[index].startswith('|') - if not is_table: - is_table = self.RE_END_BORDER.search(rows[index]) is not None - if not is_table: - break - - if is_table: - row = self._split_row(rows[1]) - is_table = (len(row) == row0_len) and set(''.join(row)) <= set('|:- ') - if is_table: - self.separator = row - - return is_table - - def run(self, parent, blocks): - """ Parse a table block and build table. """ - block = blocks.pop(0).split('\n') - header = block[0].strip() - rows = [] if len(block) < 3 else block[2:] - - # Get alignment of columns - align = [] - for c in self.separator: - c = c.strip() - if c.startswith(':') and c.endswith(':'): - align.append('center') - elif c.startswith(':'): - align.append('left') - elif c.endswith(':'): - align.append('right') - else: - align.append(None) - - # Build table - table = etree.SubElement(parent, 'table') - thead = etree.SubElement(table, 'thead') - self._build_row(header, thead, align) - tbody = etree.SubElement(table, 'tbody') - if len(rows) == 0: - # Handle empty table - self._build_empty_row(tbody, align) - else: - for row in rows: - self._build_row(row.strip(), tbody, align) - - def _build_empty_row(self, parent, align): - """Build an empty row.""" - tr = etree.SubElement(parent, 'tr') - count = len(align) - while count: - etree.SubElement(tr, 'td') - count -= 1 - - def _build_row(self, row, parent, align): - """ Given a row of text, build table cells. """ - tr = etree.SubElement(parent, 'tr') - tag = 'td' - if parent.tag == 'thead': - tag = 'th' - cells = self._split_row(row) - # We use align here rather than cells to ensure every row - # contains the same number of columns. - for i, a in enumerate(align): - c = etree.SubElement(tr, tag) - try: - c.text = cells[i].strip() - except IndexError: # pragma: no cover - c.text = "" - if a: - c.set('align', a) - - def _split_row(self, row): - """ split a row of text into list of cells. """ - if self.border: - if row.startswith('|'): - row = row[1:] - row = self.RE_END_BORDER.sub('', row) - return self._split(row) - - def _split(self, row): - """ split a row of text with some code into a list of cells. """ - elements = [] - pipes = [] - tics = [] - tic_points = [] - tic_region = [] - good_pipes = [] - - # Parse row - # Throw out \\, and \| - for m in self.RE_CODE_PIPES.finditer(row): - # Store ` data (len, start_pos, end_pos) - if m.group(2): - # \`+ - # Store length of each tic group: subtract \ - tics.append(len(m.group(2)) - 1) - # Store start of group, end of group, and escape length - tic_points.append((m.start(2), m.end(2) - 1, 1)) - elif m.group(3): - # `+ - # Store length of each tic group - tics.append(len(m.group(3))) - # Store start of group, end of group, and escape length - tic_points.append((m.start(3), m.end(3) - 1, 0)) - # Store pipe location - elif m.group(5): - pipes.append(m.start(5)) - - # Pair up tics according to size if possible - # Subtract the escape length *only* from the opening. - # Walk through tic list and see if tic has a close. - # Store the tic region (start of region, end of region). - pos = 0 - tic_len = len(tics) - while pos < tic_len: - try: - tic_size = tics[pos] - tic_points[pos][2] - if tic_size == 0: - raise ValueError - index = tics[pos + 1:].index(tic_size) + 1 - tic_region.append((tic_points[pos][0], tic_points[pos + index][1])) - pos += index + 1 - except ValueError: - pos += 1 - - # Resolve pipes. Check if they are within a tic pair region. - # Walk through pipes comparing them to each region. - # - If pipe position is less that a region, it isn't in a region - # - If it is within a region, we don't want it, so throw it out - # - If we didn't throw it out, it must be a table pipe - for pipe in pipes: - throw_out = False - for region in tic_region: - if pipe < region[0]: - # Pipe is not in a region - break - elif region[0] <= pipe <= region[1]: - # Pipe is within a code region. Throw it out. - throw_out = True - break - if not throw_out: - good_pipes.append(pipe) - - # Split row according to table delimeters. - pos = 0 - for pipe in good_pipes: - elements.append(row[pos:pipe]) - pos = pipe + 1 - elements.append(row[pos:]) - return elements - - -class TableExtension(Extension): - """ Add tables to Markdown. """ - - def extendMarkdown(self, md, md_globals): - """ Add an instance of TableProcessor to BlockParser. """ - if '|' not in md.ESCAPED_CHARS: - md.ESCAPED_CHARS.append('|') - md.parser.blockprocessors.add('table', - TableProcessor(md.parser), - ' Date: Wed, 24 Jun 2020 18:26:31 +0900 Subject: [PATCH 47/72] =?UTF-8?q?cpo=E3=81=AE=E8=A1=A8=E7=A4=BA=E5=90=8D?= =?UTF-8?q?=E3=82=92=E7=95=A5=E7=A7=B0=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E5=AE=8C=E5=85=A8=E5=90=8D=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/meta.py b/meta.py index b06aac3..501ecec 100644 --- a/meta.py +++ b/meta.py @@ -163,7 +163,11 @@ def run(self, text): if 'header' in meta: text = '
<{}>
'.format(meta['header'][0]) + text if 'id-type' in meta: - text = '
{}
'.format(meta['id-type'][0]) + text + id_type = meta['id-type'][0] + if id_type == 'cpo': + text = '
{}
'.format('customization point object') + text + else: + text = '
{}
'.format(id_type) + text return text From 9c694ae4083fc57456bee0156cf283be393af1e4 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Fri, 16 Oct 2020 17:09:13 +0900 Subject: [PATCH 48/72] =?UTF-8?q?getchildren=E9=96=A2=E6=95=B0=E3=81=8C?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=95=E3=82=8C=E3=81=9F=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=80=81=E4=BB=A3=E3=82=8F=E3=82=8A=E3=81=AE=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=81=AB=E5=B7=AE=E3=81=97=E6=9B=BF=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html_attribute.py b/html_attribute.py index 50e515c..5a55a8f 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -162,7 +162,7 @@ def __init__(self, md): def _iterate(self, elements, f): f(elements) - for child in elements.getchildren(): + for child in elements: self._iterate(child, f) def _add_color_code(self, element): From 2109c361dd6d28ebd805b2ae34e61d75427f835b Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 29 May 2022 09:50:44 +0900 Subject: [PATCH 49/72] =?UTF-8?q?Postprocessor=20"defined=5Fwords"=20?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- defined_words.py | 293 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 defined_words.py diff --git a/defined_words.py b/defined_words.py new file mode 100644 index 0000000..c866de6 --- /dev/null +++ b/defined_words.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +"""定義語をリンクに変換 +======================= + +DEFINED_WORDS.json でリンクの指定されている定義語を本文中から検索しリンクに変換 +する。 + + +xml.etree.ElementTree にまつわる実装の留意事項 +---------------------------------------------- + +markdown.treeprocessors では Python の "標準的な XML ライブラリ"である +xml.etree.ElementTree (etree) を使っているようだ (最終的にこの実装では +markdown.postprocessors を用いることにしたので必ずしも xml.etree.ElementTree を +使わなければならないわけではないが)。etree のドキュメントはあるが仕様が色々信じ +がたいので結局直接ソースコード [1] を見るのが確実である。 + +* etree は XPath を部分的に実装していると謳っている [2] が XPath 特有の機能は全 + く実装されていない。子・子孫・属性による要素の選択を XPath に似た文法で指定で + きるというだけである。XPath ならではの機能はない。textノードも抜き出せないし、 + 何なら /xxx() のような文法はないし、集合演算にも対応していない。当初、以下を + 動かそうと試行錯誤していたが全く無駄な努力だった。 + + for text in root.findall("(.//* except .//*/(a | code | pre)/*)/child::text()"): + print(text) + +* etree では要素からその親要素を取得する方法がない。なのでそもそも XPath なり何 + なりのセレクターで列挙したとしても自身を置き換えるような DOM 修正が不可能であ + る。なので、木構造を直接自前で辿るしかない。 + +* etree の実装は不思議なことに node の概念がなく、文字列は直前の開始タグまたは + 終了タグの付属物として記録されている。element.text が開始タグ直後の文字列で + element.tail が終了タグ直後の文字列である。特に、element.text は「その要素内 + に含まれる文字列全て」ではなく最初の子要素が現れるまでの文字列であるという事 + に注意する。親要素の最初の node としての文字列でないものは全て直前の要素の終 + 了タグの付属物として記録されている。例えば、 + + text1text2text3text4text5 + + に対しては + + a.text = "text1" + b.text = "text2" + b.tail = "text3" + c.text = "text4" + c.tail = "text5" + a.tail = None + + という具合に文字列が格納されている。 + + 元ソースの実体参照 ("<" など) は解決された状態 (つまり "<" など) で tail, + text に格納されると思われる。少なくとも a.text = "<"としたらソースに変換する + 時点で "<" が出力される。 + +* 子要素を iterate する方法が分からないと思ったら親要素が Iterable であり + elem.iter() で子要素のイテレータが得られる。これは変だ。 + + for e in elem: + print(e) + +* etree では子要素を追加する elem.insert(index, childElement) という関数がある。 + 引数に index を要求しているが、そもそも子要素を index 指定で取得する機能もな + いし、子要素から index を取得する機能もないので、index の指定のしようがない。 + 呼び出し側で、親要素の構築時に何番目にどの要素が格納されているかを別に記録し + ていなければどうにもならない。或いは既存の要素に対して処理する時は elem を一 + 旦 iterate して対応表を手元に作るか、private メンバ _children に直接アクセス + する必要がある (但し _children はバージョンが変わった時に変わらないとも限らな + い)。 + +* xml.dom.minidom という多少はましなものもある [3,4] 様だが xml.etree とは互換 + 性がない。木を再構築する必要がある。これを使うのだったらそもそも + markdown.treeprocessors を使う意味がない (現在は Postprocessor に移行して自前 + で xml.etree の木を構築しているのでこの際 minidom に乗り換えても良いのかもし + れない)。 + +* Q 要素を作る時は必ず elem.SubElement などの関数経由で構築する必要はあるか? + + A. 恐らくない。直接要素を構築してから追加すれば良いと思われる。質問サイトの + [5] の質疑応答を見る限りは ([5] の質問自体は今回の疑問と直接関係ないが)、取り + 合えず要素は etree.Element で作成してから append して問題ないようだ (DOM の場 + 合には、アロケータの都合だろうか、document.createElement を使う必要があったが + その様な制約はないようである)。 + +* etree では if elem: は elem に子要素が存在するかどうかで判定される。つまり、 + None かそうでないかの判定に使おうと思っていると痛い目を見る。 + +- [1] https://github.com/python/cpython/blob/main/Lib/xml/etree/ElementTree.py +- [2] https://docs.python.org/ja/3/library/xml.etree.elementtree.html#elementtree-xpath +- [3] [XMLを扱うモジュール群 — Python 3.10.4 ドキュメント](https://docs.python.org/ja/3/library/xml.html) +- [4] https://github.com/python/cpython/blob/main/Lib/xml/dom/minidom.py +- [5] https://stackoverflow.com/questions/37572695/python-etree-insert-append-and-subelement + + +その他の留意事項 +---------------- + +* Python-Markdown のプロセッサの処理順序: md.treeprocessors.register, + md.postprocessors.register の第3引数 priority に渡す値で処理の順序が変わる。 + 小さな値の方が後段で処理が実施されるようだ。postprocessors の場合 10 より小さ + な値を指定しておけば最後に実施される。 + + treeprocessors の場合、priority=1 に設定すると: + + * リンク []() は要素 a に変換された状態で渡されるので問題なくスキップできる。 + + * 実体参照は "乱数:番号" に置換された状態で渡される。つまり、<>& などを含んだ + 文字列に対して一致させることはできない? + + * htmlStash されている要素はこの時点で "乱数:番号" に変換されているので、中に + 含まれる単語について処理することはできない。 + + 実体参照や htmlStash された文字列は Postprocessor で復元される。 + Python-Markdown のソースを見ると Treeprocessors を全て処理した後に + Postprocessor が実行されるので、実体参照や htmlStash された情報を参照する処理 + は Treeprocessor ではできない。仕方がないので Postprocessor で処理することに + した。 + +""" + +from markdown.extensions import Extension +from markdown.postprocessors import Postprocessor + +import regex as re + +import xml.etree.ElementTree as etree + +# リンク・コード・見出しなどの内部は自動リンクの対象としない。除外タグ判定用正規表現 +_RE_EXCLUDED_TAGS = re.compile(r'^(?:a|code|pre|kbd|dfn|h[1-6])$', re.IGNORECASE) + +# 自動リンク対象を英単語境界に一致させる必要があるかの判定用正規表現 +_RE_WBEG = re.compile(r'^[\p{Ll}\p{Lu}_0-9]') +_RE_WEND = re.compile(r'[\p{Ll}\p{Lu}_0-9]$') + +# ソース名 (.md) からHTML名 (.html) に置換する時に使う正規表現 +_RE_LINK_EXTENSION = re.compile(r'(?:\.md)?([?#]|$)', re.MULTILINE) + + +def _quoteWordForRegex(word): + ret = re.escape(word) + if _RE_WBEG.match(word): + ret = r'(?<=^|[^\p{Ll}\p{Lu}_0-9])' + ret + if _RE_WEND.search(word): + ret = ret + r'(?=$|[^\p{Ll}\p{Lu}_0-9])' + return ret + + +class DefinedWordTreeprocessor(Postprocessor): + """A postprocessor for Python-Markdown to create links of defined words.""" + + def __init__(self, md, config): + Postprocessor.__init__(self, md) + self._markdown = md + + self.config = config + self.base_url = self.config['base_url'] + self.base_path = self.config['base_path'] + self.extension = self.config['extension'] + self._dict = self.config['dict'] + + if len(self._dict) > 0: + # Note: regex には 500 個の制限があるらしい (以下参照)。 + # https://github.com/cpprefjp/site_generator/issues/72 + # https://github.com/cpprefjp/markdown_to_html/commit/fb18c87b48c6290dd6ba00141ecb2f5dc8aba930 + if len(self._dict) > 500: + raise Exception("Too many defined words: count = %d must not be greater than 500" % len(self._dict)) + # Note: できるだけ長い一致を優先させるため逆ソートしてから正規表現にす + # る。例えば "不定|不定値" ではなく "不定値|不定" になるようにしないと、 + # 本文中の "不定値" に対して "[不定値]" とリンク付けされて欲しいが "[不 + # 定]値" とリンク付けされてしまう。 + self.re_defined_words = re.compile(r'|'.join([_quoteWordForRegex(key) for key in sorted(self._dict.keys(), reverse=True)]), re.MULTILINE) + + def _convertText(self, text): + new_text = None + ins = [] + pos = 0 + prev = None + visited = {} + for m in self.re_defined_words.finditer(text): + word = m.group(0) + if word not in self._dict or word in visited: + continue + visited[word] = True + + left = text[pos:m.start()] + if prev is not None: + prev.tail = left + else: + new_text = left + + data = self._dict[word] + attrs = {'class': 'cpprefjp-defined-word'} + if 'link' in data: + link = _RE_LINK_EXTENSION.sub(self.extension + r'\1', data['link'], count=1) + if link.startswith('/'): + link = self.base_url + link + attrs['href'] = link + if 'desc' in data: + attrs['data-desc'] = data['desc'] + a = etree.Element('a', attrs) + a.text = word + ins.append(a) + + pos = m.end() + prev = a + + left = text[pos:] + if prev is not None: + prev.tail = left + else: + new_text = left + + return new_text, ins + + def _recurseElement(self, elem): + if elem.tag is etree.Comment or elem.tag is etree.ProcessingInstruction: + return + if _RE_EXCLUDED_TAGS.match(elem.tag): + return + + insertions = [] + + if elem.text is not None: + elem.text, ins = self._convertText(elem.text) + else: + ins = [] + insertions.append(ins) + + for e in elem: + self._recurseElement(e) + if e.tail is not None: + e.tail, ins = self._convertText(e.tail) + else: + ins = [] + insertions.append(ins) + + for i, ins in reversed(list(enumerate(insertions))): + for e in reversed(ins): + elem.insert(i, e) + + def run(self, text): + """Construct ElementTree, convert and re-serialize it""" + if len(self._dict) == 0: + return + + try: + md = self._markdown + text = '<{tag}>{text}'.format(tag=md.doc_tag, text=text) + root = etree.fromstring(text) + self._recurseElement(root) + output = etree.tostring(root, encoding="unicode", method="xml") + beg = output.index('<%s>' % md.doc_tag) + len(md.doc_tag) + 2 + end = output.rindex('' % md.doc_tag) + return output[beg:end].strip() + except etree.ParseError as e: + lineno = e.position[0] + xs = text.split('\n')[lineno - 5:lineno + 5] + print('[Parse Error : {0}]'.format(self.config['full_path'])) + for x, n in zip(xs, range(lineno - 5, lineno + 5)): + print('{0:5d} {1}'.format(n + 1, x)) + raise + + +class DefinedWordExtension(Extension): + """An extension for Python-Markdown to create links of defined words.""" + + def __init__(self, **kwargs): + # define default configs + self.config = { + 'base_url': ["https://cpprefjp.github.io", + "base url of the site"], + 'base_path': ["", + "directory path that contains the current document"], + 'full_path': ["implementation-compliance.md", + "path to the source file"], + 'extension': ['.html', + "the extension of the generated HTML files"], + 'dict': [{"不適格": "/implementation-compliance.md"}, + "dictionary that maps a defined word to a link"], + } + + for key, value in kwargs.items(): + if key in self.config: + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + """Add DefinedWordTreeprocessor to Markdown instance.""" + proc = DefinedWordTreeprocessor(md, self.getConfigs()) + md.postprocessors.register(proc, 'defined_words', 1) + md.registerExtension(self) + + +def makeExtension(**kwargs): + return DefinedWordExtension(**kwargs) From 2c16d1a6e078a58ac7aaca050637636a9da95425 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 7 Jun 2022 14:50:41 +0900 Subject: [PATCH 50/72] =?UTF-8?q?defined=5Fwords:=20revert=2099a9377=20(?= =?UTF-8?q?=E5=90=84text=20node=E3=81=A7=E9=AB=98=E3=80=851=E5=9B=9E?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E3=83=AA=E3=83=B3=E3=82=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- defined_words.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/defined_words.py b/defined_words.py index c866de6..124d1be 100644 --- a/defined_words.py +++ b/defined_words.py @@ -174,13 +174,10 @@ def _convertText(self, text): ins = [] pos = 0 prev = None - visited = {} for m in self.re_defined_words.finditer(text): word = m.group(0) - if word not in self._dict or word in visited: + if word not in self._dict: continue - visited[word] = True - left = text[pos:m.start()] if prev is not None: prev.tail = left From 8a1588418fbe39d01c9eb4c5586fff57efd0f25d Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 7 Jun 2022 15:12:06 +0900 Subject: [PATCH 51/72] =?UTF-8?q?defined=5Fwords:=20"redirect"=20=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- defined_words.py | 56 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/defined_words.py b/defined_words.py index 124d1be..ac609cd 100644 --- a/defined_words.py +++ b/defined_words.py @@ -132,7 +132,10 @@ _RE_WEND = re.compile(r'[\p{Ll}\p{Lu}_0-9]$') # ソース名 (.md) からHTML名 (.html) に置換する時に使う正規表現 -_RE_LINK_EXTENSION = re.compile(r'(?:\.md)?([?#]|$)', re.MULTILINE) +_RE_LINK_EXTENSION = re.compile(r'^([^?#]+?)(?:\.md)([?#]|$)') + +# リンクに "https:" 等のスキーム名が含まれているか判定するのに使う正規表現 +_RE_LINK_SCHEME = re.compile(r'^[a-zA-Z0-9]+:') def _quoteWordForRegex(word): @@ -147,6 +150,42 @@ def _quoteWordForRegex(word): class DefinedWordTreeprocessor(Postprocessor): """A postprocessor for Python-Markdown to create links of defined words.""" + def _resolveWordProperty(self, word, prop): + if prop in self._dict[word]: + return self._dict[word][prop], None + visited = {} + while 'redirect' in self._dict[word]: + if word in visited: + raise Exception("defined_words: redirection loop for '%s'" % word) + visited[word] = True + word = self._dict[word]['redirect'] + if prop in self._dict[word]: + return self._dict[word][prop], word + return None, None + + def _resolveDictionary(self): + for word in self._dict.keys(): + entry = self._dict[word] + if 'link' not in entry: + value, redirect = self._resolveWordProperty(word, 'link') + if value is not None: + entry['link'] = value + if 'desc' not in entry: + value, redirect = self._resolveWordProperty(word, 'desc') + if value is not None: + entry['desc'] = "%s。%s" % (redirect, value) + + for word in self._dict.keys(): + entry = self._dict[word] + if 'link' in entry: + link = entry['link'] + if _RE_LINK_SCHEME.search(link) is None: + link = _RE_LINK_EXTENSION.sub(r'\1%s\2' % self.extension, link, count=1) + if not link.startswith('/'): + raise Exception("defined_words: link='%s': relative link is unallowed" % link) + link = self.base_url + link + entry['resolved_link'] = link + def __init__(self, md, config): Postprocessor.__init__(self, md) self._markdown = md @@ -169,6 +208,8 @@ def __init__(self, md, config): # 定]値" とリンク付けされてしまう。 self.re_defined_words = re.compile(r'|'.join([_quoteWordForRegex(key) for key in sorted(self._dict.keys(), reverse=True)]), re.MULTILINE) + self._resolveDictionary() + def _convertText(self, text): new_text = None ins = [] @@ -184,15 +225,12 @@ def _convertText(self, text): else: new_text = left - data = self._dict[word] + entry = self._dict[word] attrs = {'class': 'cpprefjp-defined-word'} - if 'link' in data: - link = _RE_LINK_EXTENSION.sub(self.extension + r'\1', data['link'], count=1) - if link.startswith('/'): - link = self.base_url + link - attrs['href'] = link - if 'desc' in data: - attrs['data-desc'] = data['desc'] + if 'resolved_link' in entry: + attrs['href'] = entry['resolved_link'] + if 'desc' in entry: + attrs['data-desc'] = entry['desc'] a = etree.Element('a', attrs) a.text = word ins.append(a) From e824b6caf47dc0d522f5c3749b43c4861d073746 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Fri, 23 Sep 2022 22:32:24 +0900 Subject: [PATCH 52/72] =?UTF-8?q?C++26=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/meta.py b/meta.py index 501ecec..8af81e7 100644 --- a/meta.py +++ b/meta.py @@ -98,6 +98,11 @@ def __init__(self, md): 'title': 'C++23で追加', 'text': '(C++23)', }, + 'cpp26': { + 'class_name': 'cpp26', + 'title': 'C++26で追加', + 'text': '(C++26)', + }, 'cpp11deprecated': { 'class_name': 'cpp11deprecated text-warning', 'title': 'C++11で非推奨', @@ -143,6 +148,16 @@ def __init__(self, md): 'title': 'C++23で削除', 'text': '(C++23で削除)', }, + 'cpp26deprecated': { + 'class_name': 'cpp26deprecated text-warning', + 'title': 'C++26で非推奨', + 'text': '(C++26で非推奨)', + }, + 'cpp26removed': { + 'class_name': 'cpp26removed text-danger', + 'title': 'C++26で削除', + 'text': '(C++26で削除)', + }, } def run(self, text): From 28f07ac5300a239d5c8ce52c3871da96aff3cd51 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 29 Sep 2022 16:47:57 +0900 Subject: [PATCH 53/72] =?UTF-8?q?C++=20(=E5=B0=86=E6=9D=A5)=20=E3=81=A8C++?= =?UTF-8?q?=20(=E5=BB=83=E6=A1=88)=20=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/meta.py b/meta.py index 8af81e7..ecb858f 100644 --- a/meta.py +++ b/meta.py @@ -73,6 +73,16 @@ def __init__(self, md): self._markdown = md CPP_DIC = { + 'future': { + 'class_name': 'future', + 'title': '将来のC++として検討中', + 'text': '(将来のC++機能)', + }, + 'archive': { + 'class_name': 'archive', + 'title': '廃案になったC++機能', + 'text': '(廃案のC++機能)', + }, 'cpp11': { 'class_name': 'cpp11', 'title': 'C++11で追加', From 8f0410485ca8d4d13e17c3f8f5b135131c18973d Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Tue, 29 Nov 2022 19:16:34 +0900 Subject: [PATCH 54/72] =?UTF-8?q?=E8=A6=8B=E5=87=BA=E3=81=97=E3=81=AB?= =?UTF-8?q?=E3=82=82=E7=94=A8=E8=AA=9E=E5=AE=9A=E7=BE=A9=E3=81=AE=E3=83=84?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=83=81=E3=83=83=E3=83=97=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 適格要件やテンプレートパラメータ制約などのため --- defined_words.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defined_words.py b/defined_words.py index ac609cd..1e3fd18 100644 --- a/defined_words.py +++ b/defined_words.py @@ -125,7 +125,7 @@ import xml.etree.ElementTree as etree # リンク・コード・見出しなどの内部は自動リンクの対象としない。除外タグ判定用正規表現 -_RE_EXCLUDED_TAGS = re.compile(r'^(?:a|code|pre|kbd|dfn|h[1-6])$', re.IGNORECASE) +_RE_EXCLUDED_TAGS = re.compile(r'^(?:a|code|pre|kbd|dfn)$', re.IGNORECASE) # 自動リンク対象を英単語境界に一致させる必要があるかの判定用正規表現 _RE_WBEG = re.compile(r'^[\p{Ll}\p{Lu}_0-9]') From 533bf5f9eb2abc21bf17f2ef90ac29aaab44b498 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Mon, 13 May 2024 15:19:56 +0900 Subject: [PATCH 55/72] =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=88=E3=83=AB=E3=82=92=E3=82=AD=E3=83=BC=E3=83=AF?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=83=AA=E3=83=B3=E3=82=AF=E3=81=AE=E5=AF=BE?= =?UTF-8?q?=E8=B1=A1=E3=81=8B=E3=82=89=E5=A4=96=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- defined_words.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defined_words.py b/defined_words.py index 1e3fd18..3a37990 100644 --- a/defined_words.py +++ b/defined_words.py @@ -124,8 +124,8 @@ import xml.etree.ElementTree as etree -# リンク・コード・見出しなどの内部は自動リンクの対象としない。除外タグ判定用正規表現 -_RE_EXCLUDED_TAGS = re.compile(r'^(?:a|code|pre|kbd|dfn)$', re.IGNORECASE) +# リンク・コード・タイトルなどの内部は自動リンクの対象としない。除外タグ判定用正規表現 +_RE_EXCLUDED_TAGS = re.compile(r'^(?:a|code|pre|kbd|dfn|h1)$', re.IGNORECASE) # 自動リンク対象を英単語境界に一致させる必要があるかの判定用正規表現 _RE_WBEG = re.compile(r'^[\p{Ll}\p{Lu}_0-9]') From 09e3f636938ea9edb05877dafa7a0b7b73391619 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 29 May 2022 18:04:49 +0900 Subject: [PATCH 56/72] =?UTF-8?q?html=5Fattribute:=20USE=5FRELATIVE=5FLINK?= =?UTF-8?q?=20=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index 5a55a8f..1f5ade6 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -3,6 +3,7 @@ markdown から変換した HTML に属性を追加する """ +import posixpath import re import sys @@ -156,10 +157,16 @@ def basic_escape(self, html): class AttributePostprocessor(postprocessors.Postprocessor): - def __init__(self, md): + def __init__(self, md, config): postprocessors.Postprocessor.__init__(self, md) self._markdown = md + self.config = config + self.re_url_hash = re.compile(r'#.*$') + self.url_base = self.config['base_url'].strip('/') + '/' + self.url_current = self.url_base + self._remove_md(self.config['full_path']) + self.url_current_base = self.url_base + self.config['base_path'].strip('/') + def _iterate(self, elements, f): f(elements) for child in elements: @@ -260,6 +267,21 @@ def _to_absolute_url(self, element): sys.stderr.write('Warning: [{full_path}] href "{url} ({check_href})" not found.\n'.format(**locals())) element.tag = 'span' + def _to_relative_url(self, element): + if element.tag == 'a' and 'href' in element.attrib: + href = element.attrib['href'] + if self.re_url_hash.sub("", href) == self.url_current: + element.attrib['href'] = href[len(self.url_current):] + elif href.startswith(self.url_base): + element.attrib['href'] = posixpath.relpath(href, self.url_current_base) + + def _adjust_url(self, element): + self._to_absolute_url(element) + + # 一旦絶対パスに統一してから相対パスに変換する + if self.config['use_relative_link']: + self._to_relative_url(element) + def _add_meta(self, element): body = etree.Element('div', itemprop="articleBody") after_h1 = False @@ -285,7 +307,7 @@ def run(self, text): raise # self._iterate(root, self._add_color_code) self._iterate(root, self._add_border_table) - self._iterate(root, self._to_absolute_url) + self._iterate(root, self._adjust_url) self._add_meta(root) output = self._markdown.serializer(root) @@ -313,13 +335,13 @@ def __init__(self, **kwargs): 'base_path': ['', "Base Path used to link URL as relative URL"], 'full_path': ['', "Full Path used to link URL as anchor URL"], 'extension': ['', "URL extension"], + 'use_relative_link': [False, "Whether to use relative paths for domestic links"] } super().__init__(**kwargs) def extendMarkdown(self, md, md_globals): - attr = AttributePostprocessor(md) - attr.config = self.getConfigs() + attr = AttributePostprocessor(md, self.getConfigs()) md.postprocessors.add('html_attribute', attr, '_end') md.postprocessors['raw_html'] = SafeRawHtmlPostprocessor(md) From 3d0aa6bc512e60b51f97e3c572a7f0a5410e5750 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 6 Jun 2022 04:37:47 +0900 Subject: [PATCH 57/72] =?UTF-8?q?html=5Fattribute:=20image=E3=83=AA?= =?UTF-8?q?=E3=83=9D=E3=82=B8=E3=83=88=E3=83=AA=E5=8F=82=E7=85=A7URL?= =?UTF-8?q?=E3=82=92=E8=A7=A3=E6=B1=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html_attribute.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/html_attribute.py b/html_attribute.py index 1f5ade6..43f172a 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -167,6 +167,10 @@ def __init__(self, md, config): self.url_current = self.url_base + self._remove_md(self.config['full_path']) self.url_current_base = self.url_base + self.config['base_path'].strip('/') + image_repo = self.config['image_repo'] + self.re_url_github_image = re.compile(r'^https?://(?:raw.github.com/%s/master|github.com/%s/raw)/' % (image_repo, image_repo)) + self.image_base = 'https://raw.githubusercontent.com/%s/master/' % image_repo + def _iterate(self, elements, f): f(elements) for child in elements: @@ -275,6 +279,18 @@ def _to_relative_url(self, element): elif href.startswith(self.url_base): element.attrib['href'] = posixpath.relpath(href, self.url_current_base) + def _resolve_image_src(self, element): + if element.tag == 'img' and 'src' in element.attrib: + src = element.attrib['src'] + src = self.re_url_github_image.sub(self.image_base, src, count=1) + if self.config['use_static_image'] and src.startswith(self.image_base): + src = 'static/image/' + src[len(self.image_base):] + if self.config['use_relative_link']: + src = posixpath.relpath(self.url_base + src, self.url_current_base) + else: + src = '/' + src + element.attrib['src'] = src + def _adjust_url(self, element): self._to_absolute_url(element) @@ -282,6 +298,8 @@ def _adjust_url(self, element): if self.config['use_relative_link']: self._to_relative_url(element) + self._resolve_image_src(element) + def _add_meta(self, element): body = etree.Element('div', itemprop="articleBody") after_h1 = False @@ -335,7 +353,9 @@ def __init__(self, **kwargs): 'base_path': ['', "Base Path used to link URL as relative URL"], 'full_path': ['', "Full Path used to link URL as anchor URL"], 'extension': ['', "URL extension"], - 'use_relative_link': [False, "Whether to use relative paths for domestic links"] + 'use_relative_link': [False, "Whether to use relative paths for domestic links"], + 'image_repo': ['cpprefjp/image', "Name of GitHub repository that contains the images"], + 'use_static_image': [False, "Whether to use the images in static/image instead on GitHub"] } super().__init__(**kwargs) From 160e85e0a535dc4d35781d7e8547c532c321e1e8 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Tue, 11 Jun 2024 13:33:40 +0900 Subject: [PATCH 58/72] =?UTF-8?q?=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E7=94=A8=E3=81=AE=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/cpprefjp/site/issues/1274 --- mark.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 mark.py diff --git a/mark.py b/mark.py new file mode 100644 index 0000000..d4b3e24 --- /dev/null +++ b/mark.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +絵文字の置き換え +========================================= + +絵文字を以下の方針に従って置き換える: +・表示できない環境を考慮する (アクセシビリティ) +・絵文字の意味をツールチップで表示する + + >>> text = "GCC: 12.0 [mark noimpl], 13.1 [mark impl], 14.1 [mark verified]" + >>> md = markdown.Markdown(['mark']) + >>> print md.convert(text) + GCC: 12.0 , 13.1 , 14.1 +""" + +import re + +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + + +MARK_DICT = { + "[mark noimpl]": "", + "[mark impl]": "", + "[mark verified]": "", +} + +class MarkExtension(Extension): + + def extendMarkdown(self, md, md_globals): + markpre = MarkPreprocessor(md) + + md.registerExtension(self) + md.preprocessors.add('meta', markpre, ">normalize_whitespace") + + +class MarkPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + self._markdown = md + + def run(self, lines): + new_lines = [] + self._markdown._meta_result = {} + pattern = re.compile("|".join(map(re.escape, MARK_DICT.keys()))) + + for line in lines: + new_line = pattern.sub(lambda match: MARK_DICT[match.group(0)], line) + new_lines.append(new_line) + + return new_lines + + +def makeExtension(**kwargs): + return MarkExtension(**kwargs) From f4185dac822ad6475844f12a7a06add2cd27b590 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Tue, 11 Jun 2024 17:55:04 +0900 Subject: [PATCH 59/72] =?UTF-8?q?mark.py=20:=20=E3=81=BE=E3=81=A1=E3=81=8C?= =?UTF-8?q?=E3=81=A3=E3=81=A6meta=E3=82=92=E4=B8=8A=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=97=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/cpprefjp/site/issues/1274 --- mark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mark.py b/mark.py index d4b3e24..3d49ce9 100644 --- a/mark.py +++ b/mark.py @@ -31,7 +31,7 @@ def extendMarkdown(self, md, md_globals): markpre = MarkPreprocessor(md) md.registerExtension(self) - md.preprocessors.add('meta', markpre, ">normalize_whitespace") + md.preprocessors.add('mark', markpre, ">normalize_whitespace") class MarkPreprocessor(Preprocessor): From f95588d91c0db77615cce7a1a5bdcc0712069bc5 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 18 Jul 2024 22:14:25 +0900 Subject: [PATCH 60/72] =?UTF-8?q?=E6=8E=B2=E8=BC=89=E6=9C=9F=E9=99=90?= =?UTF-8?q?=E3=82=92=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=AB=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E7=94=A8=E3=81=AE=E6=A7=8B=E6=96=87=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/cpprefjp/site_generator/issues/87 --- sponsor.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 sponsor.py diff --git a/sponsor.py b/sponsor.py new file mode 100644 index 0000000..435ecb4 --- /dev/null +++ b/sponsor.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +スポンサー表示 +========================================= + +スポンサーを掲載期限付きで表示する +- name : スポンサー名を指定する。ロゴ画像があればaltとして、なければ名前表示 (required) +- img : ロゴ画像へのURLを指定する (optional) +- link : リンク先URL (optional) +- size : ロゴ画像のピクセル幅サイズ (optional) +- period : スポンサーの掲載期限 (required) +- fee : 金額。生成HTMLには影響なし。スポンサーの並び替え用 (required) +- amount : 数量。月額の場合は12、1回限りは1。スポンサーの並び替え用 (optional) +- note : メモ (optional) + + >>> text = "[sponsor name:NAME, img:IMAGE_URL, link:LINK_URL, size:PIXEL_SIZE, period:YYYY/MM/DD, amount:MONEY]" + >>> md = markdown.Markdown(['mark']) + >>> print md.convert(text) +
NAME
+ + >>> text = "[sponsor name:NAME, link:LINK_URL]" + >>> md = markdown.Markdown(['mark']) + >>> print md.convert(text) + +""" + +import re +import datetime + +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + + +def replace_sponsor_line(line: str, now: datetime.datetime) -> str: + m = re.search(r'\[sponsor (.*?)\]', line) + if not m: + return line + + dict = {} + for x in m[1].split(", "): + y = x.split(":") + dict[y[0]] = ":".join(y[1:]) + + # check expired (one-time or canceled) + if dict.get("period"): + period = datetime.datetime.fromisoformat(dict["period"] + " 23:59:59.000000+09:00") + if now > period: + return line.replace(m[0], "") + + if dict.get("img") is None: + new_sponsor = "" + if dict.get("link") is None: + new_sponsor = "
  • {}
".format(dict["name"]) + else: + new_sponsor = "".format(dict["link"], dict["name"]) + return line.replace(m[0], new_sponsor) + + img = "" + center = "" + center_close = "" + center = "
" + center_close = "
" + img = "\"{}\"{}/".format( + dict["img"], + dict["name"], + " width=\"{}\"".format(dict["size"]) if dict.get("size") is not None else "" + ) + link = "" + link_close = "" + if dict.get("link") is not None: + link = "".format(dict["link"]) + link_close = "" + + new_sponsor = center + link + img + link_close + center_close + return line.replace(m[0], new_sponsor) + +class SponsorExtension(Extension): + + def extendMarkdown(self, md, md_globals): + pre = SponsorPreprocessor(md) + + md.registerExtension(self) + md.preprocessors.add('sponsor', pre, ">normalize_whitespace") + + +class SponsorPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + self._markdown = md + + def run(self, lines): + new_lines = [] + self._markdown._meta_result = {} + + jst = datetime.timezone(datetime.timedelta(hours=+9), 'JST') + now = datetime.datetime.now(jst) + + for line in lines: + new_line = replace_sponsor_line(line, now) + new_lines.append(new_line) + + return new_lines + + +def makeExtension(**kwargs): + return SponsorExtension(**kwargs) From 2ce6b711d8d7c3c7cc1e8b74f8a300388b759f44 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Thu, 1 Aug 2024 22:40:01 +0900 Subject: [PATCH 61/72] =?UTF-8?q?sponsor=20:=20=E7=94=BB=E5=83=8F=E3=81=AE?= =?UTF-8?q?=E3=83=84=E3=83=BC=E3=83=AB=E3=83=81=E3=83=83=E3=83=97=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E5=90=8D=E3=82=92=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sponsor.py b/sponsor.py index 435ecb4..ef40335 100644 --- a/sponsor.py +++ b/sponsor.py @@ -60,7 +60,7 @@ def replace_sponsor_line(line: str, now: datetime.datetime) -> str: center_close = "" center = "
" center_close = "
" - img = "\"{}\"{}/".format( + img = "\"{1}\"".format( dict["img"], dict["name"], " width=\"{}\"".format(dict["size"]) if dict.get("size") is not None else "" From 66b84e4dde219857d19552b969c846d9493c9d1a Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 30 May 2022 22:02:30 +0900 Subject: [PATCH 62/72] =?UTF-8?q?html=5Fattribute:=20markdown.searialiers?= =?UTF-8?q?=20=E3=81=AE=E5=86=85=E9=83=A8=E5=A4=89=E6=95=B0=E3=82=92?= =?UTF-8?q?=E4=B8=80=E6=99=82=E7=9A=84=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=97?= =?UTF-8?q?=E3=81=A6=E5=AE=9F=E4=BD=93=E5=8F=82=E7=85=A7=E3=82=92=E9=81=A9?= =?UTF-8?q?=E5=88=87=E3=81=AB=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit これまでは self._markdown.serializer が使われていたが、serializer は何 故か実体参照のパターンに一致する & を & に変換せずにそのまま出力す る仕様になっている [1]。これを用いてHTMLに戻すと例えば元々のHTMLソース で &var; となっていたものが処理後のHTMLソースで不正な実体参照 &var; になってしまい、ブラウザで正しく表示されなくなる可能性がある。例 えば reference/memory/addressof.html でこれが起こっている [2]。さらに、 後段でHTMLを解釈して処理しようとしたときにクラッシュする。 xml.etree.ElementTree が etree.tostring(method="xml") で正当なHTMLへの 変換に対応しているが、これを用いると などが になって しまう。代わりに method="html" を使うと
などが
になってし まう。また、どちらの method の場合でも HTML 属性の順序が変わってしまう ので好ましくない。 この実装では代わりに markdown.searializers の内部変数 markdown.searializers.RE_AMP を書き換えることによって期待する動作を得 ている。上流の markdown.searializers で実装の変更があると動かなくなる 可能性があることに注意する。 [1] https://github.com/Python-Markdown/markdown/blob/a11431539d08e14b0bd821ceb101fa59d6a74c8a/markdown/serializers.py#L69-L70 [2] https://github.com/cpprefjp/cpprefjp.github.io/blob/335489b522dcbb648c19a49e39cc880e187911a5/reference/memory/addressof.html#L263 --- html_attribute.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/html_attribute.py b/html_attribute.py index 43f172a..850ab44 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -9,6 +9,7 @@ import markdown from markdown import postprocessors +from markdown import serializers import xml.etree.ElementTree as etree @@ -312,6 +313,32 @@ def _add_meta(self, element): element.remove(e) element.append(body) + def _tohtml(self, element): + # Note: 以下の様に etree.tostring(method="xml") を用いると + # や が や になってしまう。また、 + # HTML 属性の順序が保持されない。 + # + # return etree.tostring(element, encoding="unicode", method="xml") + + # Note: 代わりに etree.tostring(method="html") を用いると、今度は

になってしまい好ましくない。またこの時 + # も HTML 属性の順序が保持されない。 + # + # return etree.tostring(element, encoding="unicode", method="html") + + # 今は代わりに以下のようにして markdown.serializers の内部変数 + # markdown.serializers.RE_AMP を一時的に書き換えることによって期待する + # 動作を得ている。これは markdown.serializers の内部実装に依存している + # ので、markdown.serializers の上流で内部実装に変更があると動かなくなる + # 可能性があることに注意する。 + old_RE_AMP = serializers.RE_AMP + try: + serializers.RE_AMP = re.compile(r'&') + output = self._markdown.serializer(element) + finally: + serializers.RE_AMP = old_RE_AMP + return output + def run(self, text): text = '<{tag}>{text}'.format(tag=self._markdown.doc_tag, text=text) try: @@ -328,7 +355,7 @@ def run(self, text): self._iterate(root, self._adjust_url) self._add_meta(root) - output = self._markdown.serializer(root) + output = self._tohtml(root) if self._markdown.stripTopLevelTags: try: start = output.index('<%s>' % self._markdown.doc_tag) + len(self._markdown.doc_tag) + 2 From 46c8876787fae68e591a84ec1b06a18f3d711b6f Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Sun, 1 Dec 2024 15:04:44 +0900 Subject: [PATCH 63/72] =?UTF-8?q?=E8=B2=A2=E7=8C=AE=E3=83=9D=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=83=88=E9=9B=86=E8=A8=88=E7=94=A8=E3=81=AEcommit?= =?UTF-8?q?=E6=A7=8B=E6=96=87=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commit.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 commit.py diff --git a/commit.py b/commit.py new file mode 100644 index 0000000..a04aa00 --- /dev/null +++ b/commit.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +コミット構文 +========================================= + +コミットIDをリンクに変換する +[commit REPOSITORY_NAME, commit-id0, commit-id-2...] + + >>> text = "[commit REPOSITORY_NAME, 1234567, abcdefg]" + >>> md = markdown.Markdown(['commit']) + >>> print md.convert(text) + 1234567 abcdefg +""" + +import re + +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + + +def replace_commit_line(line: str) -> str: + new_line: str = line + for m in re.finditer(r'\[commit (.*?)\]', line.strip()): + c = m[1].split(", ") + repo = c[0] + links: list[str] = [] + for id in c[1:]: + id = id.strip() + if len(id) == 0: + continue + links.append("{1}".format(repo, id)) + commits: str = " ".join(links) + new_line = new_line.replace(m[0], commits) + return new_line + +class CommitExtension(Extension): + + def extendMarkdown(self, md, md_globals): + pre = CommitPreprocessor(md) + + md.registerExtension(self) + md.preprocessors.add('commit', pre, ">normalize_whitespace") + + +class CommitPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + self._markdown = md + + def run(self, lines): + new_lines = [] + self._markdown._meta_result = {} + + for line in lines: + new_line = replace_commit_line(line) + new_lines.append(new_line) + + return new_lines + + +def makeExtension(**kwargs): + return CommitExtension(**kwargs) From 4332fb9df0da35049e1e470faccf9fe9d3d6d9df Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 10 Dec 2024 20:47:55 +0900 Subject: [PATCH 64/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=83=96?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E5=BE=8C=E3=81=AE=E4=BF=AE=E9=A3=BE?= =?UTF-8?q?=E3=81=AE=E6=8A=BD=E5=87=BA=E3=82=92=E5=8E=B3=E5=AF=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref. [1] で発見された問題。コードブロック後に改行を挟まずに文章を続け るとその文章がコード修飾指定のセクションとして抽出されて消失する問題。 対処法として改行を挿入する [2, 3] ことで回避できるが、改行がなくても正 しく表示されるようにしたい。ここでは、コード修飾の指定についてより厳密 な形式で抽出を行い、誤って関係ない物を抽出することを防ぐ [4]。 この変更のテストの過程で、既存のコード修飾の誤りが発見されたが対処した [5]。また、他にも消失している記述 [6] が発見されたが、[6] はこの変更に より自動的に修正されるので対処はしていない。 References: [1] https://github.com/cpprefjp/site/issues/1362#issuecomment-2482124298 [2] https://github.com/cpprefjp/site/commit/c747f4a71f9ab8a4dfaf1a21424e58bd5ed73f55 [3] https://github.com/cpprefjp/site/commit/5259ff6bc67ca8b95d5b9450555a4ca2de97a3ee [4] https://github.com/cpprefjp/markdown_to_html/pull/8#issuecomment-2527599901 [5] https://github.com/cpprefjp/site/commit/ebf8c8fd705a194adb7b3f83786dad9c843d143b [6] https://github.com/cpprefjp/site/blob/ebf8c8fd705a194adb7b3f83786dad9c843d143b/reference/generator/generator/iterator/op_increment.md?plain=1#L27 --- qualified_fenced_code.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 6509299..4eaf079 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -41,7 +41,17 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)(?P.*?)\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n(?:(?=\n)|(?P.*?\n(?=\s*\n)))', re.MULTILINE | re.DOTALL) +# qualifier の各行は以下の形式を持つことを要求する。箇条書きまたは番号リストの +# 項目であり、[meta ...], [mathjax enable ...], [link ...], [color ...], +# [italic] の何れかの修飾子が含まれていること。インデントレベルは少なくとも ``` +# と同じであること。 +QUALIFIER_LINE_RE_STRING = r'(?P=indent)\s*(?:[-*+]|[0-9]+\.)\s[^\n]*\[(?:meta|mathjax enable|link|color|italic)\b[^\n]*\][^\n]*\n' + +# 以下の正規表現は qualifier 行の連続を規定する。最初の qualifier が、閉じ ``` +# と同じレベルの箇条書きまたは番号リストの項目でなければそこで中断する。 +QUALIFIERS_RE_STRING = r'(?:(?!(?P=indent)(?:[-*+]|[0-9]+\.)\s)|(?P(?:%s)*))' % QUALIFIER_LINE_RE_STRING + +QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)(?P.*?)\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n' + QUALIFIERS_RE_STRING, re.MULTILINE | re.DOTALL) QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') INDENT_RE = re.compile(r'^[ \t]+', re.MULTILINE) From 941d376cacf8e9edb6c2e81c3ab0d25951b968b2 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 10 Dec 2024 22:02:44 +0900 Subject: [PATCH 65/72] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E4=BF=AE?= =?UTF-8?q?=E9=A3=BE=E3=81=AE=E7=AE=87=E6=9D=A1=E6=9B=B8=E3=81=8D=E3=81=AF?= =?UTF-8?q?=20*=20=E3=81=A7=E3=81=82=E3=82=8B=E3=81=93=E3=81=A8=E3=82=92?= =?UTF-8?q?=E8=A6=81=E8=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qualified_fenced_code.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 4eaf079..2cde1cd 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -41,15 +41,15 @@ CODE_WRAP = '
%s
' LANG_TAG = ' class="%s"' -# qualifier の各行は以下の形式を持つことを要求する。箇条書きまたは番号リストの -# 項目であり、[meta ...], [mathjax enable ...], [link ...], [color ...], -# [italic] の何れかの修飾子が含まれていること。インデントレベルは少なくとも ``` -# と同じであること。 -QUALIFIER_LINE_RE_STRING = r'(?P=indent)\s*(?:[-*+]|[0-9]+\.)\s[^\n]*\[(?:meta|mathjax enable|link|color|italic)\b[^\n]*\][^\n]*\n' +# qualifier の各行は以下の形式を持つことを要求する。"*" による箇条書きの項目で +# あり、[meta ...], [mathjax enable ...], [link ...], [color ...], [italic] の +# 何れかの修飾子が含まれていること。インデントレベルは少なくとも閉じ ``` と同じ +# であること。 +QUALIFIER_LINE_RE_STRING = r'(?P=indent)\s*\*\s[^\n]*\[(?:meta|mathjax enable|link|color|italic)\b[^\n]*\][^\n]*\n' # 以下の正規表現は qualifier 行の連続を規定する。最初の qualifier が、閉じ ``` -# と同じレベルの箇条書きまたは番号リストの項目でなければそこで中断する。 -QUALIFIERS_RE_STRING = r'(?:(?!(?P=indent)(?:[-*+]|[0-9]+\.)\s)|(?P(?:%s)*))' % QUALIFIER_LINE_RE_STRING +# と同じレベルの "*" による箇条書きの項目でなければそこで中断する。 +QUALIFIERS_RE_STRING = r'(?:(?!(?P=indent)\*\s)|(?P(?:%s)*))' % QUALIFIER_LINE_RE_STRING QUALIFIED_FENCED_BLOCK_RE = re.compile(r'(?P`{3,})[ ]*(?P[a-zA-Z0-9_+-]*)(?P.*?)\n(?P.*?)(?<=\n)(?P[ \t]*)(?P=fence)[ ]*\n' + QUALIFIERS_RE_STRING, re.MULTILINE | re.DOTALL) QUALIFY_COMMAND_RE = re.compile(r'\[(.*?)\]') From e2af656da07c963510edcd1971460c283d3a3575 Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Mon, 16 Dec 2024 10:42:46 +0900 Subject: [PATCH 66/72] =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E5=B4=A9=E3=82=8C?= =?UTF-8?q?=E3=82=92=E4=BA=8B=E5=89=8D=E4=BF=AE=E6=AD=A3=E3=81=99=E3=82=8B?= =?UTF-8?q?Markdown=E6=8B=A1=E5=BC=B5=E3=82=92=E8=BF=BD=E5=8A=A0=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commit.py | 4 +-- fix_display_error.py | 75 ++++++++++++++++++++++++++++++++++++++++ footer.py | 3 +- mark.py | 4 +-- mathjax.py | 2 +- meta.py | 4 +-- qualified_fenced_code.py | 2 +- sponsor.py | 4 +-- 8 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 fix_display_error.py diff --git a/commit.py b/commit.py index a04aa00..0e426a7 100644 --- a/commit.py +++ b/commit.py @@ -39,18 +39,16 @@ def extendMarkdown(self, md, md_globals): pre = CommitPreprocessor(md) md.registerExtension(self) - md.preprocessors.add('commit', pre, ">normalize_whitespace") + md.preprocessors.register(pre, 'commit', 25) class CommitPreprocessor(Preprocessor): def __init__(self, md): Preprocessor.__init__(self, md) - self._markdown = md def run(self, lines): new_lines = [] - self._markdown._meta_result = {} for line in lines: new_line = replace_commit_line(line) diff --git a/fix_display_error.py b/fix_display_error.py new file mode 100644 index 0000000..5a3c04b --- /dev/null +++ b/fix_display_error.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +表示崩れを事前修正 +========================================= + +Markdownライブラリの以下の制限を回避: + +- 箇条書きの前に空行が必要な制限を回避して、自動で空行を挟む +""" + +import re +import datetime + +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor + +def is_item_line(line: str) -> bool: + stripped_line = line.strip() + m = re.match(r'^([0-9]+\.\s)', stripped_line) + if m: + return True + + m = re.match(r'^([*+-]\s)', stripped_line) + if m: + return True + return False + +def is_item_end_line(line: str) -> bool: + if len(line) == 0: + return True + if re.match(r'^#+ ', line): + return True + return False + +class FixDisplayErrorExtension(Extension): + + def extendMarkdown(self, md, md_globals): + pre = FixDisplayErrorPreprocessor(md) + + md.registerExtension(self) + md.preprocessors.register(pre, 'fix_display_error', 28) + + +class FixDisplayErrorPreprocessor(Preprocessor): + + def __init__(self, md): + Preprocessor.__init__(self, md) + + def run(self, lines): + new_lines = [] + + prev_line: str | None = None + in_item: bool = False + for line in lines: + if prev_line == None: + prev_line = line + new_lines.append(line) + continue + + if not is_item_line(prev_line) and not in_item and is_item_line(line): + new_lines.append("") + + if not in_item and is_item_line(line): + in_item = True + if in_item and is_item_end_line(line): + in_item = False + + prev_line = line + new_lines.append(line) + + return new_lines + + +def makeExtension(**kwargs): + return FixDisplayErrorExtension(**kwargs) diff --git a/footer.py b/footer.py index 6920132..d3ee452 100644 --- a/footer.py +++ b/footer.py @@ -20,7 +20,8 @@ def extendMarkdown(self, md, md_globals): footer = FooterTreeprocessor() footer.config = self.getConfigs() md.registerExtension(self) - md.treeprocessors.add('footer', footer, '_begin') + #md.treeprocessors.add('footer', footer, '_begin') + md.treeprocessors.register(footer, 'footer', 50) # top priority (begin) class FooterTreeprocessor(markdown.treeprocessors.Treeprocessor): diff --git a/mark.py b/mark.py index 3d49ce9..e317a59 100644 --- a/mark.py +++ b/mark.py @@ -31,18 +31,16 @@ def extendMarkdown(self, md, md_globals): markpre = MarkPreprocessor(md) md.registerExtension(self) - md.preprocessors.add('mark', markpre, ">normalize_whitespace") + md.preprocessors.register(markpre, 'mark', 25) class MarkPreprocessor(Preprocessor): def __init__(self, md): Preprocessor.__init__(self, md) - self._markdown = md def run(self, lines): new_lines = [] - self._markdown._meta_result = {} pattern = re.compile("|".join(map(re.escape, MARK_DICT.keys()))) for line in lines: diff --git a/mathjax.py b/mathjax.py index 09af179..68b923f 100644 --- a/mathjax.py +++ b/mathjax.py @@ -28,7 +28,7 @@ def extendMarkdown(self, md, md_globals): mathjaxpre = MathJaxPreprocessor(md) md.registerExtension(self) - md.preprocessors.add('mathjax', mathjaxpre, ">normalize_whitespace") + md.preprocessors.register(mathjaxpre, 'mathjax', 25) class MathJaxPreprocessor(Preprocessor): diff --git a/meta.py b/meta.py index ecb858f..6994d92 100644 --- a/meta.py +++ b/meta.py @@ -40,8 +40,8 @@ def extendMarkdown(self, md, md_globals): metapost = MetaPostprocessor(md) md.registerExtension(self) - md.preprocessors.add('meta', metapre, ">normalize_whitespace") - md.postprocessors.add('meta', metapost, '_end') + md.preprocessors.register(metapre, 'meta', 25) + md.postprocessors.register(metapost, 'meta', 0) # bottom priority (end) class MetaPreprocessor(Preprocessor): diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index 2cde1cd..ddeb4ac 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -65,7 +65,7 @@ def extendMarkdown(self, md, md_globals): fenced_block = QualifiedFencedBlockPreprocessor(md, self.global_qualify_list) md.registerExtension(self) - md.preprocessors.add('qualified_fenced_code', fenced_block, ">normalize_whitespace") + md.preprocessors.register(fenced_block, 'qualified_fenced_code', 29) def _make_random_string(): diff --git a/sponsor.py b/sponsor.py index ef40335..64e8dd9 100644 --- a/sponsor.py +++ b/sponsor.py @@ -80,18 +80,16 @@ def extendMarkdown(self, md, md_globals): pre = SponsorPreprocessor(md) md.registerExtension(self) - md.preprocessors.add('sponsor', pre, ">normalize_whitespace") + md.preprocessors.register(pre, 'sponsor', 25) class SponsorPreprocessor(Preprocessor): def __init__(self, md): Preprocessor.__init__(self, md) - self._markdown = md def run(self, lines): new_lines = [] - self._markdown._meta_result = {} jst = datetime.timezone(datetime.timedelta(hours=+9), 'JST') now = datetime.datetime.now(jst) From 5603611484f427628212c63c35731140df952b27 Mon Sep 17 00:00:00 2001 From: Raclamusi Date: Sat, 4 Oct 2025 16:45:52 +0900 Subject: [PATCH 67/72] =?UTF-8?q?C++=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=83=90=E3=83=83=E3=82=B8=E3=81=AEtypo?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meta.py b/meta.py index 6994d92..fc40ed3 100644 --- a/meta.py +++ b/meta.py @@ -125,8 +125,8 @@ def __init__(self, md): }, 'cpp14removed': { 'class_name': 'cpp14removed text-danger', - 'title': 'C++11で削除', - 'text': '(C++11で削除)', + 'title': 'C++14で削除', + 'text': '(C++14で削除)', }, 'cpp17deprecated': { 'class_name': 'cpp17deprecated text-warning', From 962b43970d0c0e20192278b2545c670e8e6cc49d Mon Sep 17 00:00:00 2001 From: Raclamusi Date: Sat, 4 Oct 2025 17:43:08 +0900 Subject: [PATCH 68/72] =?UTF-8?q?C++=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=83=90=E3=83=83=E3=82=B8=E3=81=AB=E3=80=8C?= =?UTF-8?q?C++11=E3=81=A7=E5=89=8A=E9=99=A4=E3=80=8D=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meta.py b/meta.py index fc40ed3..2e03d52 100644 --- a/meta.py +++ b/meta.py @@ -118,6 +118,11 @@ def __init__(self, md): 'title': 'C++11で非推奨', 'text': '(C++11で非推奨)', }, + 'cpp11removed': { + 'class_name': 'cpp11removed text-danger', + 'title': 'C++11で削除', + 'text': '(C++11で削除)', + }, 'cpp14deprecated': { 'class_name': 'cpp14deprecated text-warning', 'title': 'C++14で非推奨', From c99ca1e2d2f5052f6b23cdf2998b5d4f5582613c Mon Sep 17 00:00:00 2001 From: Akira Takahashi Date: Tue, 13 Jan 2026 16:49:53 +0900 Subject: [PATCH 69/72] =?UTF-8?q?exposition-only=E3=81=AE=E6=96=9C?= =?UTF-8?q?=E4=BD=93=E3=83=BB=E3=83=90=E3=83=83=E3=82=B8=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meta.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meta.py b/meta.py index 2e03d52..e1b6d0d 100644 --- a/meta.py +++ b/meta.py @@ -198,6 +198,11 @@ def run(self, text): text = '
{}
'.format('customization point object') + text else: text = '
{}
'.format(id_type) + text + if 'exposition-only' in meta: + # 見出しを斜体にするためのクラスを追加 + text = text.replace('

', '

') + # 説明専用バッジを追加 + text = text.replace('

', '') return text From addee970644203f5feca7a79909bb95a19e5aea6 Mon Sep 17 00:00:00 2001 From: take-cheeze Date: Tue, 2 Jun 2026 17:04:54 +0900 Subject: [PATCH 70/72] Update for markdown 3.8 --- commit.py | 2 +- defined_words.py | 2 +- fix_display_error.py | 2 +- footer.py | 2 +- html_attribute.py | 7 ++++--- mark.py | 2 +- mathjax.py | 6 +++--- meta.py | 2 +- qualified_fenced_code.py | 8 ++++---- sponsor.py | 2 +- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/commit.py b/commit.py index 0e426a7..0a9ac94 100644 --- a/commit.py +++ b/commit.py @@ -35,7 +35,7 @@ def replace_commit_line(line: str) -> str: class CommitExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): pre = CommitPreprocessor(md) md.registerExtension(self) diff --git a/defined_words.py b/defined_words.py index 3a37990..78eb620 100644 --- a/defined_words.py +++ b/defined_words.py @@ -317,7 +317,7 @@ def __init__(self, **kwargs): if key in self.config: self.setConfig(key, value) - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): """Add DefinedWordTreeprocessor to Markdown instance.""" proc = DefinedWordTreeprocessor(md, self.getConfigs()) md.postprocessors.register(proc, 'defined_words', 1) diff --git a/fix_display_error.py b/fix_display_error.py index 5a3c04b..b35e5e9 100644 --- a/fix_display_error.py +++ b/fix_display_error.py @@ -34,7 +34,7 @@ def is_item_end_line(line: str) -> bool: class FixDisplayErrorExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): pre = FixDisplayErrorPreprocessor(md) md.registerExtension(self) diff --git a/footer.py b/footer.py index d3ee452..d0b6f30 100644 --- a/footer.py +++ b/footer.py @@ -16,7 +16,7 @@ def __init__(self, configs): for key, value in configs: self.setConfig(key, value) - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): footer = FooterTreeprocessor() footer.config = self.getConfigs() md.registerExtension(self) diff --git a/html_attribute.py b/html_attribute.py index 850ab44..6f97f25 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -387,10 +387,11 @@ def __init__(self, **kwargs): super().__init__(**kwargs) - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): attr = AttributePostprocessor(md, self.getConfigs()) - md.postprocessors.add('html_attribute', attr, '_end') - md.postprocessors['raw_html'] = SafeRawHtmlPostprocessor(md) + md.postprocessors.register(attr, 'html_attribute', 0) + md.postprocessors.deregister('raw_html') + md.postprocessors.register(SafeRawHtmlPostprocessor(md), 'raw_html', 30) def makeExtension(**kwargs): diff --git a/mark.py b/mark.py index e317a59..d3ff1b8 100644 --- a/mark.py +++ b/mark.py @@ -27,7 +27,7 @@ class MarkExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): markpre = MarkPreprocessor(md) md.registerExtension(self) diff --git a/mathjax.py b/mathjax.py index 68b923f..41c2150 100644 --- a/mathjax.py +++ b/mathjax.py @@ -24,7 +24,7 @@ class MathJaxExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): mathjaxpre = MathJaxPreprocessor(md) md.registerExtension(self) @@ -57,7 +57,7 @@ def run(self, lines): if not m: break tex = m.group(0) - placeholder = self.markdown.htmlStash.store(code_escape(tex)) + placeholder = self.md.htmlStash.store(code_escape(tex)) text = text[:m.start()] + placeholder + text[m.end():] lines3 = [] @@ -68,7 +68,7 @@ def run(self, lines): if not m: break tex = m.group(0) - placeholder = self.markdown.htmlStash.store(code_escape(tex)) + placeholder = self.md.htmlStash.store(code_escape(tex)) line = line[:m.start()] + placeholder + line[m.end():] lines3.append(line) diff --git a/meta.py b/meta.py index e1b6d0d..060a85e 100644 --- a/meta.py +++ b/meta.py @@ -35,7 +35,7 @@ class MetaExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): metapre = MetaPreprocessor(md) metapost = MetaPostprocessor(md) diff --git a/qualified_fenced_code.py b/qualified_fenced_code.py index ddeb4ac..bf0472a 100644 --- a/qualified_fenced_code.py +++ b/qualified_fenced_code.py @@ -61,7 +61,7 @@ class QualifiedFencedCodeExtension(Extension): def __init__(self, global_qualify_list): self.global_qualify_list = global_qualify_list - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): fenced_block = QualifiedFencedBlockPreprocessor(md, self.global_qualify_list) md.registerExtension(self) @@ -263,7 +263,7 @@ def __init__(self, md, global_qualify_list): def run(self, lines): # Check for code hilite extension if not self.checked_for_codehilite: - for ext in self.markdown.registeredExtensions: + for ext in self.md.registeredExtensions: if isinstance(ext, CodeHiliteExtension): self.codehilite_conf = ext.config break @@ -288,7 +288,7 @@ def run(self, lines): # サンプルコードだったら、self.markdown の中にコードの情報と ID を入れておく if is_example: example_id = hashlib.sha1((str(example_counter) + code).encode('utf-8')).hexdigest() - self.markdown._example_codes.append({"id": example_id, "code": code}) + self.md._example_codes.append({"id": example_id, "code": code}) example_counter += 1 qualifier_list = QualifierList(qualifies) @@ -319,7 +319,7 @@ def run(self, lines): code = qualifier_list.qualify(code) - placeholder = self.markdown.htmlStash.store(code) + placeholder = self.md.htmlStash.store(code) text = '%s\n%s\n%s' % (text[:m.start()], placeholder, text[m.end():]) else: break diff --git a/sponsor.py b/sponsor.py index 64e8dd9..e4bfb53 100644 --- a/sponsor.py +++ b/sponsor.py @@ -76,7 +76,7 @@ def replace_sponsor_line(line: str, now: datetime.datetime) -> str: class SponsorExtension(Extension): - def extendMarkdown(self, md, md_globals): + def extendMarkdown(self, md): pre = SponsorPreprocessor(md) md.registerExtension(self) From 802af99c3860a4278e4c5b71645aa8ec1e326f7e Mon Sep 17 00:00:00 2001 From: take-cheeze Date: Tue, 2 Jun 2026 17:39:53 +0900 Subject: [PATCH 71/72] Fix error --- html_attribute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html_attribute.py b/html_attribute.py index 6f97f25..8f372fb 100644 --- a/html_attribute.py +++ b/html_attribute.py @@ -130,11 +130,11 @@ class SafeRawHtmlPostprocessor(postprocessors.Postprocessor): HTML_TAG_RE = re.compile(r'^\<\/?([a-zA-Z0-9]+)[^\>]*\>$') def run(self, text): - for i in range(self.markdown.htmlStash.html_counter): - html = self.markdown.htmlStash.rawHtmlBlocks[i] + for i in range(self.md.htmlStash.html_counter): + html = self.md.htmlStash.rawHtmlBlocks[i] # if not safe: # html = self.escape(html) - text = text.replace(self.markdown.htmlStash.get_placeholder(i), html) + text = text.replace(self.md.htmlStash.get_placeholder(i), html) return text def escape(self, html): From bfb4188dfbc0f89cf588bb114d70c6883930084d Mon Sep 17 00:00:00 2001 From: take-cheeze Date: Thu, 4 Jun 2026 17:00:44 +0900 Subject: [PATCH 72/72] Remove submodule --- .gitmodules | 3 --- markdown_to_html | 1 - 2 files changed, 4 deletions(-) delete mode 160000 markdown_to_html diff --git a/.gitmodules b/.gitmodules index 3fba50b..de1d23b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "markdown_to_html"] - path = markdown_to_html - url = https://github.com/cpprefjp/markdown_to_html.git [submodule "crsearch"] path = crsearch url = git@github.com:cpprefjp/crsearch.git diff --git a/markdown_to_html b/markdown_to_html deleted file mode 160000 index 802af99..0000000 --- a/markdown_to_html +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 802af99c3860a4278e4c5b71645aa8ec1e326f7e