diff --git a/CrossDown/Core.py b/CrossDown/Core.py
new file mode 100644
index 0000000..7301ec9
--- /dev/null
+++ b/CrossDown/Core.py
@@ -0,0 +1,336 @@
+from markdown.extensions import Extension, extra, admonition, meta, sane_lists, toc, wikilinks, codehilite
+
+from pygments.formatters import HtmlFormatter
+
+from markdown.treeprocessors import Treeprocessor
+from markdown.inlinepatterns import Pattern as Pattern_
+from markdown.preprocessors import Preprocessor
+from markdown.inlinepatterns import InlineProcessor
+from markdown.blockprocessors import BlockProcessor
+from markdown import Markdown
+from typing import *
+import re
+import xml
+import emoji
+
+
+try: # 检测当前平台是否支持扩展语法
+ from .Extra import *
+ EXTRA_ABLE = True
+except ModuleNotFoundError: # 不支持扩展语法
+ EXTRA_ABLE = False
+
+
+class HighlightHtmlFormatter(HtmlFormatter):
+ def __init__(self, lang_str='', **options):
+ super().__init__(**options)
+ # lang_str has the value {lang_prefix}{lang}
+ # specified by the CodeHilite's options
+ self.lang_str = lang_str.split('-')[-1]
+
+ def _wrap_code(self, source):
+ yield 0, f''
+ yield from source
+ yield 0, '
'
+
+
+Extensions = {
+ '基本扩展': extra.ExtraExtension(fenced_code={'lang_prefix': ''}),
+ '警告扩展': admonition.AdmonitionExtension(),
+ '元数据': meta.MetaExtension(),
+ '能列表': sane_lists.SaneListExtension(),
+ '目录': toc.TocExtension(),
+ '内部链接': wikilinks.WikiLinkExtension(),
+ '代码高亮': codehilite.CodeHiliteExtension(guess_lang=False, pygments_formatter=HighlightHtmlFormatter),
+}
+
+
+class Simple(InlineProcessor):
+ """
+ 可通过简单的正则表达式和HTML标签实现的样式
+ """
+
+ def __init__(self, pattern: str, tag: str):
+ """
+ 初始化
+ :param pattern: 正则表达式
+ :param tag: html标签
+ """
+ super().__init__(pattern)
+ self.tag = tag
+
+ def handleMatch(self, match, match_line):
+ tag = xml.etree.ElementTree.Element(self.tag) # 创建标签
+ tag.text = match.group(1) # 获取匹配到的文本并设置为标签的内容
+
+ return tag, match.start(), match.end()
+
+
+class Nest(InlineProcessor):
+ """
+ 需要嵌套HTML标签实现的样式
+ """
+
+ def __init__(self, pattern: str, outer_tag: str, inner_tag: str):
+ """
+ 初始化
+ :param pattern: 正则表达式
+ :param outer_tag: 外层html标签
+ :param inner_tag: 内层html标签
+ """
+ super().__init__(pattern)
+ self.outer_tag = outer_tag
+ self.inner_tag = inner_tag
+
+ def handleMatch(self, match, match_line):
+ outer_tag = xml.etree.ElementTree.Element(self.outer_tag) # 创建外层标签
+ inner_tag = xml.etree.ElementTree.SubElement(outer_tag, self.inner_tag) # 创建内层标签
+ outer_tag.text = match.group(1) # 设置外层标签文本
+ inner_tag.text = match.group(2) # 设置内层标签文本
+
+ return outer_tag, match.start(), match.end()
+
+
+class ID(InlineProcessor):
+ """
+ 需要对HTML标签设置ID实现的样式
+ """
+
+ def __init__(self, pattern: str, tag: str, property_: str, value: Union[str, bool] = None):
+ """
+ 初始化
+ :param pattern: 正则表达式
+ :param tag: html标签
+ :param property_: html标签属性名称
+ :param value: html标签属性的值 不设置时为第二个匹配组,设置为整数时则为指定的匹配组,设置为字符串则为原始字符串
+ """
+ super().__init__(pattern)
+ self.tag = tag
+ self.property = property_
+ self.value = value
+
+ def handleMatch(self, match, match_line):
+ tag = xml.etree.ElementTree.Element(self.tag) # 创建标签
+ tag.text = match.group(1) # 设置标签内容
+ tag.set(self.property, match.group(2) if self.value is None else self.value) # 设置标签属性,属性的值默认为第二个匹配组
+
+ return tag, match.start(), match.end()
+
+
+class Emoji(InlineProcessor):
+ """
+ 需要对HTML标签设置ID实现的样式
+ """
+
+ def __init__(self, pattern: str):
+ """
+ 初始化
+ :param pattern: 正则表达式
+ """
+ super().__init__(pattern)
+
+ def handleMatch(self, match, match_line):
+ return emoji.emojize(match.group(0)), match.start(), match.end()
+
+
+class Syllabus(BlockProcessor):
+ # 定义提纲的正则表达式
+ syllabus_re = r'(\d+(\.\d+)*)\s+(.*)'
+
+ def test(self, parent, block):
+ # 检查当前块是否匹配我们的正则表达式
+ return re.match(self.syllabus_re, block)
+
+ def run(self, parent, blocks):
+ syllabus = re.match(self.syllabus_re, blocks[0]) # 匹配提纲的号和内容
+ header = xml.etree.ElementTree.SubElement(parent, f'h{len(syllabus.group(1).split("."))}') # 按照提纲号等级创建标题
+ header.set('id', syllabus.group(1)) # 设置提纲ID
+ header.text = syllabus.group(1) + ' ' + syllabus.group(3) # 设置提纲内容
+ blocks[0] = ''
+ return False
+
+
+class BoxBlock(BlockProcessor):
+ def __init__(self, parser, re_start, re_end, style):
+ super().__init__(parser)
+ self.re_start = re_start # start line, e.g., ` !!!!
+ self.re_end = re_end # last non-blank line, e.g, '!!!\n \n\n'
+ self.style = style
+
+ def test(self, parent, block):
+ return re.match(self.re_start, block)
+
+ def run(self, parent, blocks):
+ original_block = blocks[0]
+ blocks[0] = re.sub(self.re_start, '', blocks[0])
+
+ # Find block with ending fence
+ for block_num, block in enumerate(blocks):
+ if re.search(self.re_end, block):
+ # remove fence
+ blocks[block_num] = re.sub(self.re_end, '', block)
+ # render fenced area inside a new div
+ e = xml.etree.ElementTree.SubElement(parent, 'div')
+ e.set('style', self.style)
+ self.parser.parseBlocks(e, blocks[0:block_num + 1])
+ # remove used blocks
+ for i in range(0, block_num + 1):
+ blocks.pop(0)
+ return True # or could have had no return statement
+ # No closing marker! Restore and do nothing
+ blocks[0] = original_block
+ return False # equivalent to our test() routine returning False
+
+
+class _Anchor(InlineProcessor):
+ def handleMatch(self, match, match_line):
+ tag = xml.etree.ElementTree.Element('span') # 创建标签
+ tag.text = match.group(1)
+ tag.set('id', match.group(1)) # 设置id
+
+ return tag, match.start(), match.end()
+
+
+class LinkLine(InlineProcessor):
+ def handleMatch(self, match, match_line):
+ tag = xml.etree.ElementTree.Element('a') # 创建标签
+ tag.set('href', '#' + match.group(1)) # 设置id
+ tag.text = match.group(1)
+
+ return tag, match.start(), match.end()
+
+
+class CodeLine(Treeprocessor):
+ def __init__(self, variable: Dict):
+ super().__init__()
+ self.variable = variable
+
+ def run(self, root):
+ for elem in root.iter('p'): # 在所有段落中查找单行代码
+ if elem.findall('code'): # 找到单行代码
+ for code in elem:
+ if re.match(r'\$[^$]*\$', code.text): # 渲染Latex
+ if isinstance(elem.text, str): # 这个段落还有其它内容
+ elem.text += fr'\({code.text[1:-1]}\){code.tail}' # 插入latex
+ else:
+ elem.text = fr'\({code.text[1:-1]}\)' # latex是段落中唯一的内容
+ elem.remove(code)
+ elif re.match(r'¥[^$]*¥', code.text): # 是数学函数(单行)
+ if EXTRA_ABLE: # 支持扩展语法
+ expression, range_ = re.findall(r'¥([^$]*)¥(€[^$]*€)?', code.text)[0] # 分离表达式与范围(如果有)
+ x_r = (-10, 10)
+ y_r = (-20, 20)
+ if range_ != '': # 定义了范围
+ ranges = range_[1:-1].split('|')
+ if len(ranges) in (1, 2): # 定义的范围正确
+ x_r = tuple(int(i) for i in ranges[0].split(','))
+ if len(ranges) == 2: # 定义了y范围
+ y_r = tuple(int(i) for i in ranges[1].split(','))
+ code.tag = 'img'
+ code.set('src', f"""data:image/png;base64,{(function_drawing(
+ function=lambda x: eval(expression.split('=')[1]), x_range=x_r, y_range=y_r
+ ))}""") # 绘制函数图像
+ code.set('alt', 'Base64 函数图片')
+ else: # 不支持扩展语法
+ code.tag = 'span'
+ code.set('class', 'block')
+ code.text = '该平台不支持扩展语法'
+ elif re.match(r'\{[^$]*}', code.text): # 是强调
+ code.tag = 'span'
+ code.set('class', 'block')
+ key = code.text[1:-1] # 去掉两边的{}
+ if key in self.variable:
+ code.text = self.variable[key]
+ else:
+ code.text = key
+
+
+class CodeBlock(Treeprocessor):
+ def run(self, root):
+ for code in root.findall('p'):
+ # 在这里处理
标签 + # 例如,你可以添加属性或修改内容 + print(f'{code.text} | {code.tag}') + + +class Basic(Extension): # TODO InlineProcessor 不能渲染一行中两个以上的元素(内置的扩展斜体和粗体的优先级好像是一样的) + """ + 渲染基本样式 + """ + + def extendMarkdown(self, md): + md.registerExtension(self) # 注册扩展 + md.inlinePatterns.register(Simple(r'~~(.*?)~~', tag='s'), 'strikethrough', 1) # ~~删除线~~ + md.inlinePatterns.register(Simple(r'~(.*?)~', tag='u'), 'underline', 2) # ~下划线~ + md.inlinePatterns.register(Simple(r'==(.*?)==', tag='mark'), 'high_light', 3) # ==高亮== + md.inlinePatterns.register(Nest( + r'\[(.*?)]\^\((.*?)\)', outer_tag='ruby', inner_tag='rt'), 'up', 4 + ) # [在文本的正上方添加一行小文本]^(主要用于标拼音) + md.inlinePatterns.register(ID( + r'\[(.*?)]-\((.*?)\)', tag='span', property_='title'), 'hide', 5 + ) # [在指定的文本里面隐藏一段文本]-(只有鼠标放在上面才会显示隐藏文本) + md.inlinePatterns.register(Emoji(r':(.+?):'), 'emoji', 6) # 将emoji短代码转换为emoji字符 + md.parser.blockprocessors.register(Syllabus(md.parser), 'syllabus', 11) # 渲染提纲 + + +class Box(Extension): + """ + 渲染外框 + """ + + def extendMarkdown(self, md): + md.registerExtension(self) # 注册扩展 + # 红框警告 + md.inlinePatterns.register(ID( + r'!{3}(.+?)!{3}', tag='div', property_='style', value='display: inline-block; border: 1px solid red;' + ), 'warning_in_line', 20) # 行内 + md.parser.blockprocessors.register(BoxBlock( + md.parser, r'^ *!{3} *\n', r'\n *!{3}\s*$', 'display: inline-block; border: 1px solid red;' + ), 'warning_box', 175) # 块 + + # 黄框提醒 + md.inlinePatterns.register(ID( + r'!-!(.+?)!-!', tag='div', property_='style', value='display: inline-block; border: 1px solid yellow;' + ), 'reminding_in_line', 21) # 行内 + md.parser.blockprocessors.register(BoxBlock( + md.parser, r'^ *!-! *\n', r'\n *!-!\s*$', 'display: inline-block; border: 1px solid yellow;' + ), 'reminding_box', 176) # 块 + + # 绿框安心 + md.inlinePatterns.register(ID( + r',{3}(.+?),{3}', tag='div', property_='style', value='display: inline-block; border: 1px solid green;' + ), 'reminding_in_line', 22) # 行内 + md.parser.blockprocessors.register(BoxBlock( + md.parser, r'^ *,{3} *\n', r'\n *,{3}\s*$', 'display: inline-block; border: 1px solid green;' + ), 'reminding_box', 177) # 块 + + # 蓝框怀疑 + md.inlinePatterns.register(ID( + r',-,(.+?),{2}', tag='div', property_='style', value='display: inline-block; border: 1px solid blue;' + ), 'reminding_in_line', 23) # 行内 + md.parser.blockprocessors.register(BoxBlock( + md.parser, r'^ *,-, *\n', r'\n *,-,\s*$', 'display: inline-block; border: 1px solid blue;' + ), 'reminding_box', 178) # 块 + + +class Anchor(Extension): + def extendMarkdown(self, md: Markdown): + md.registerExtension(self) # 注册扩展 + md.inlinePatterns.register(_Anchor(r'\{#([^{}#]+)}'), 'anchor', 0) # 定义锚点 + md.inlinePatterns.register(LinkLine(r'\{([^{}#]+)}'), 'line_link', 0) # 添加页内链接 + + +class Code(Extension): + def __init__(self, variable: Dict): + super().__init__() + self.variable = variable + + def extendMarkdown(self, md: Markdown): + md.registerExtension(self) # 注册扩展 + md.treeprocessors.register(CodeLine(variable=self.variable), 'code_line', 0) # 渲染单行代码块 + # md.treeprocessors.register(CodeBlock(), 'code_block', 1) # 渲染多行代码块 + + +def main(text: str, variable: Dict) -> Tuple[str, Dict[str, List[str]]]: + md = Markdown(extensions=[Basic(), Box(), Anchor()] + list(Extensions.values()) + [Code(variable=variable)]) + return md.convert(text), md.Meta diff --git a/CrossDown/CrossDown.py b/CrossDown/CrossDown.py deleted file mode 100644 index 3c8321f..0000000 --- a/CrossDown/CrossDown.py +++ /dev/null @@ -1,4 +0,0 @@ -from markdown.extensions import Extension -from markdown.treeprocessors import Treeprocessor -from markdown.inlinepatterns import Pattern -from markdown import Markdown diff --git a/CrossDown/Extra.py b/CrossDown/Extra.py new file mode 100644 index 0000000..40efbb1 --- /dev/null +++ b/CrossDown/Extra.py @@ -0,0 +1,43 @@ +import matplotlib.pyplot as plt +import numpy as np +import base64 +from io import BytesIO + + +EXTRA = [ + +] + + +def function_drawing(function, x_range=(-10, 10), y_range=(-20, 20), dpi=100): + # 创建一个图像和坐标轴对象 + fig, ax = plt.subplots() + + # 生成x值 + x = np.linspace(x_range[0], x_range[1], 400) + + # 计算y值 + y = function(x) + + # 绘制图像 + ax.plot(x, y) + + # 设置坐标轴范围 + ax.set_xlim(x_range) + ax.set_ylim(y_range) + + # 隐藏坐标轴 + ax.axis('on') + + # 将图像保存到BytesIO对象 + buf = BytesIO() + fig.savefig(buf, format='png', dpi=dpi) + + # 获取图像数据的Base64编码 + data = base64.b64encode(buf.getbuffer()).decode("ascii") + + # 关闭图像和坐标轴对象 + plt.close(fig) + + # 返回Base64编码的字符串 + return data diff --git a/CrossDown/__init__.py b/CrossDown/__init__.py index e69de29..b108b75 100644 --- a/CrossDown/__init__.py +++ b/CrossDown/__init__.py @@ -0,0 +1,60 @@ +from typing import * +from .Core import main + + +__all__ = [ + 'main', # 主函数 + 'indent', # 添加空格 + 'HEAD', # + 'BODY', # +] +__version__ = '0.11.2' +__author__ = 'CrossDark' +__email__ = 'liuhanbo333@icloud.com' +__source__ = 'https://crossdark.net/' +__license__ = """MIT""" + + +HEAD = ( + '', + '', + '', + '', + + # mermaid + '', + '', + + # Highlight.js + # '', + # '', + # '', + + '', + + '' +) + +BODY = ( + '', +) + + +def indent(input_: Union[str, List, Tuple], indent_spaces: int = 4) -> str: + """ + 给字符串中的每一行前面加上缩进。 + :param input_: 原始字符串,可以包含多行。 + :param indent_spaces: 每行前面要添加的空格数,默认为4。 + :return: 带缩进的新字符串。 + """ + # 使用字符串的splitlines()方法分割原始字符串为行列表,如果是可迭代对象则直接遍历 + # 遍历行列表,给每行前面加上相应的缩进,并重新组合成字符串 + return "\n".join( + f"{' ' * indent_spaces}{line}" for line in (lambda x: x.splitlines() if isinstance(x, str) else x)(input_)) diff --git a/CrossDown.py b/CrossDown_.py similarity index 99% rename from CrossDown.py rename to CrossDown_.py index eddaf31..a161f34 100644 --- a/CrossDown.py +++ b/CrossDown_.py @@ -6,7 +6,6 @@ import markdown try: # 检测当前平台是否支持扩展语法 import CrossMore - EXTRA_ABLE = True except ModuleNotFoundError: EXTRA_ABLE = False diff --git a/README.html b/README.html index 8b77047..527208b 100644 --- a/README.html +++ b/README.html @@ -9,6 +9,10 @@ + +