md2bbcode 0.1.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- md2bbcode/__about__.py +1 -0
- md2bbcode/__init__.py +0 -0
- md2bbcode/eqlog.md +36 -0
- md2bbcode/html2bbcode.py +143 -0
- md2bbcode/main.py +66 -0
- md2bbcode/md2ast.py +44 -0
- md2bbcode/renderers/__init__.py +0 -0
- md2bbcode/renderers/bbcode.py +222 -0
- md2bbcode-0.1.0.dist-info/METADATA +126 -0
- md2bbcode-0.1.0.dist-info/RECORD +13 -0
- md2bbcode-0.1.0.dist-info/WHEEL +5 -0
- md2bbcode-0.1.0.dist-info/entry_points.txt +4 -0
- md2bbcode-0.1.0.dist-info/licenses/LICENSE.txt +674 -0
md2bbcode/__about__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
md2bbcode/__init__.py
ADDED
|
File without changes
|
md2bbcode/eqlog.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# EQLogParser
|
|
2
|
+
Everquest Log Parser for Live/TLP servers with basic support for P99.
|
|
3
|
+
|
|
4
|
+
Link to DOWNLOAD the latest Installer:</br>
|
|
5
|
+
https://github.com/kauffman12/EQLogParser/raw/master/Release/EQLogParser-install-2.2.48.exe
|
|
6
|
+
|
|
7
|
+
Minimum Requirements:
|
|
8
|
+
1. Windows 10 x64
|
|
9
|
+
2. .Net 8.0 Desktop Runtime for x64
|
|
10
|
+
|
|
11
|
+
.Net 8.0 is provided by Microsoft but is not included with Windows. It can be downloaded from here:</br>
|
|
12
|
+
https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.7-windows-x64-installer
|
|
13
|
+
|
|
14
|
+
If Everquest is in windowed mode but the Damage Meter or other overlays are hidden when you switch to the game make sure Overlap with Taskbar is turned off:</br>
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
Note for Developers:</br>
|
|
19
|
+
Syncfusion components used by this application require a license. If you apply for a community license you should be able to get one for free.
|
|
20
|
+
|
|
21
|
+
Additional Notes:</br>
|
|
22
|
+
The installer for EQLogParser has been signed with a certificate. It's recommended that the following steps are done ONCE so that you're sure you have an official version. After your system trusts the certificate you'll notice the install prompt will be blue in color and no longer say Unknown Publisher. Then in the future if it returns to yellow/Unknown Publisher you'll know that the installer either wasn't from me or I had to change certificates. Which I will mention here if I have change them. Note that this is not required and if you don't bother installing the certs everything should still work fine.
|
|
23
|
+
|
|
24
|
+
1. right-click the exe file and choose properties
|
|
25
|
+
2. under the digital signatures tab select the one signature and click details
|
|
26
|
+
3. click View Certificate
|
|
27
|
+
4. click Install Certificate
|
|
28
|
+
|
|
29
|
+
Lastly, EQLogParser-2.2.13.msi in the Releases folder can be ignored by most people. It exists to allow auto update to work from old versions of the parser. Old parsers were hard coded to look for MSI files and this one just wraps the exe installer for 2.2.13. Then the next time the parser is started it will auto update to the latest exe installer.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Example
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
# Damage Meter and Timer Overlay Example
|
|
36
|
+

|
md2bbcode/html2bbcode.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# converts some HTML tags to BBCode
|
|
2
|
+
# pass --debug to save the output to readme.finalpass
|
|
3
|
+
# may be better off replacing this with html to markdown (and then to bbcode). Lepture recommeds a JS html to markdown converter: sundown
|
|
4
|
+
from bs4 import BeautifulSoup
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
def handle_font_tag(tag, replacements):
|
|
8
|
+
"""Handles the conversion of <font> tag with attributes like color and size."""
|
|
9
|
+
attributes = []
|
|
10
|
+
if 'color' in tag.attrs:
|
|
11
|
+
attributes.append(f"COLOR={tag['color']}")
|
|
12
|
+
if 'size' in tag.attrs:
|
|
13
|
+
attributes.append(f"SIZE={tag['size']}")
|
|
14
|
+
if 'face' in tag.attrs:
|
|
15
|
+
attributes.append(f"FONT={tag['face']}")
|
|
16
|
+
|
|
17
|
+
inner_content = ''.join(recursive_html_to_bbcode(child, replacements) for child in tag.children)
|
|
18
|
+
if attributes:
|
|
19
|
+
# Nest all attributes. Example: [COLOR=red][SIZE=5]content[/SIZE][/COLOR]
|
|
20
|
+
for attr in reversed(attributes):
|
|
21
|
+
inner_content = f"[{attr}]{inner_content}[/{attr.split('=')[0]}]"
|
|
22
|
+
return inner_content
|
|
23
|
+
|
|
24
|
+
def handle_style_tag(tag, replacements):
|
|
25
|
+
"""Handles the conversion of tags with style attributes like color, size, and font."""
|
|
26
|
+
attributes = []
|
|
27
|
+
style = tag.attrs.get('style', '')
|
|
28
|
+
|
|
29
|
+
# Extracting CSS properties
|
|
30
|
+
css_properties = {item.split(':')[0].strip(): item.split(':')[1].strip() for item in style.split(';') if ':' in item}
|
|
31
|
+
|
|
32
|
+
# Mapping CSS properties to BBCode
|
|
33
|
+
if 'color' in css_properties:
|
|
34
|
+
attributes.append(f"COLOR={css_properties['color']}")
|
|
35
|
+
if 'font-size' in css_properties:
|
|
36
|
+
attributes.append(f"SIZE={css_properties['font-size']}")
|
|
37
|
+
if 'font-family' in css_properties:
|
|
38
|
+
attributes.append(f"FONT={css_properties['font-family']}")
|
|
39
|
+
if 'text-decoration' in css_properties and 'line-through' in css_properties['text-decoration']:
|
|
40
|
+
attributes.append("S") # Assume strike-through
|
|
41
|
+
if 'text-decoration' in css_properties and 'underline' in css_properties['text-decoration']:
|
|
42
|
+
attributes.append("U")
|
|
43
|
+
if 'font-weight' in css_properties:
|
|
44
|
+
if css_properties['font-weight'].lower() == 'bold' or (css_properties['font-weight'].isdigit() and int(css_properties['font-weight']) >= 700):
|
|
45
|
+
attributes.append("B") # Assume bold
|
|
46
|
+
|
|
47
|
+
inner_content = ''.join(recursive_html_to_bbcode(child, replacements) for child in tag.children)
|
|
48
|
+
if attributes:
|
|
49
|
+
# Nest all attributes
|
|
50
|
+
for attr in reversed(attributes):
|
|
51
|
+
if '=' in attr: # For attributes with values
|
|
52
|
+
inner_content = f"[{attr}]{inner_content}[/{attr.split('=')[0]}]"
|
|
53
|
+
else: # For simple BBCode tags like [B], [I], [U], [S]
|
|
54
|
+
inner_content = f"[{attr}]{inner_content}[/{attr}]"
|
|
55
|
+
return inner_content
|
|
56
|
+
|
|
57
|
+
def recursive_html_to_bbcode(tag, replacements):
|
|
58
|
+
"""Recursively convert HTML content of a given tag to BBCode."""
|
|
59
|
+
if tag.name is None:
|
|
60
|
+
return str(tag)
|
|
61
|
+
elif tag.name == 'br':
|
|
62
|
+
# Directly return a newline for <br> or </br> tags
|
|
63
|
+
return '\n'
|
|
64
|
+
elif tag.name in replacements:
|
|
65
|
+
bb_tag = replacements[tag.name]
|
|
66
|
+
inner_content = ''
|
|
67
|
+
for child in tag.children:
|
|
68
|
+
inner_content += recursive_html_to_bbcode(child, replacements)
|
|
69
|
+
|
|
70
|
+
if tag.name in ['a', 'img']:
|
|
71
|
+
if tag.name == 'a':
|
|
72
|
+
href = tag.get('href', '')
|
|
73
|
+
return f"[URL={href}]{inner_content}[/URL]"
|
|
74
|
+
elif tag.name == 'img':
|
|
75
|
+
src = tag.get('src', '')
|
|
76
|
+
alt = tag.get('alt', '')
|
|
77
|
+
if alt:
|
|
78
|
+
return f"[IMG alt=\"{alt}\"]{src}[/IMG]"
|
|
79
|
+
else:
|
|
80
|
+
return f"[IMG]{src}[/IMG]"
|
|
81
|
+
elif tag.name in ['ul', 'ol']:
|
|
82
|
+
return f"[{bb_tag}]{inner_content}[/LIST]"
|
|
83
|
+
elif tag.name == 'font':
|
|
84
|
+
# Special handling for <font> tag with attributes
|
|
85
|
+
return handle_font_tag(tag, replacements) # Pass replacements here
|
|
86
|
+
elif tag.name == 'li':
|
|
87
|
+
return f"[*]{inner_content}"
|
|
88
|
+
else:
|
|
89
|
+
return f"[{bb_tag}]{inner_content}[/{bb_tag}]"
|
|
90
|
+
elif tag.name in ['span', 'div']:
|
|
91
|
+
return handle_style_tag(tag, replacements)
|
|
92
|
+
else:
|
|
93
|
+
# For tags not in the replacements, concatenate the content
|
|
94
|
+
return ''.join(recursive_html_to_bbcode(child, replacements) for child in tag.children)
|
|
95
|
+
|
|
96
|
+
def html_to_bbcode(html):
|
|
97
|
+
replacements = {
|
|
98
|
+
'b': 'B',
|
|
99
|
+
'strong': 'B',
|
|
100
|
+
'i': 'I',
|
|
101
|
+
'em': 'I',
|
|
102
|
+
'u': 'U',
|
|
103
|
+
's': 'S',
|
|
104
|
+
'sub': 'SUB',
|
|
105
|
+
'sup': 'SUP',
|
|
106
|
+
'p': '', # Handled by default
|
|
107
|
+
'ul': 'LIST',
|
|
108
|
+
'ol': 'LIST=1',
|
|
109
|
+
'li': '*', # Special handling in recursive function
|
|
110
|
+
'font': '', # To be handled for attributes
|
|
111
|
+
'blockquote': 'QUOTE',
|
|
112
|
+
'pre': 'CODE',
|
|
113
|
+
'code': 'ICODE',
|
|
114
|
+
'a': 'URL', # Special handling for attributes
|
|
115
|
+
'img': 'IMG' # Special handling for attributes
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
soup = BeautifulSoup(html, 'html.parser')
|
|
119
|
+
return recursive_html_to_bbcode(soup, replacements)
|
|
120
|
+
|
|
121
|
+
def process_html(input_html, debug=False, output_file=None):
|
|
122
|
+
converted_bbcode = html_to_bbcode(input_html)
|
|
123
|
+
|
|
124
|
+
if debug:
|
|
125
|
+
with open(output_file, 'w', encoding='utf-8') as file:
|
|
126
|
+
file.write(converted_bbcode)
|
|
127
|
+
else:
|
|
128
|
+
return converted_bbcode
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
parser = argparse.ArgumentParser(description="Convert HTML to BBCode with optional debugging output.")
|
|
132
|
+
parser.add_argument('input_file', type=str, help='Input HTML file path')
|
|
133
|
+
parser.add_argument('--debug', action='store_true', help='Save output to readme.finalpass for debugging')
|
|
134
|
+
|
|
135
|
+
args = parser.parse_args()
|
|
136
|
+
input_file = args.input_file
|
|
137
|
+
output_file = 'readme.finalpass' if args.debug else None
|
|
138
|
+
|
|
139
|
+
with open(input_file, 'r', encoding='utf-8') as file:
|
|
140
|
+
html_content = file.read()
|
|
141
|
+
|
|
142
|
+
# Call the processing function
|
|
143
|
+
process_html(html_content, debug=args.debug, output_file=output_file)
|
md2bbcode/main.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# uses a custom mistune renderer to convert Markdown to BBCode. The custom renderer is defined in the bbcode.py file.
|
|
2
|
+
# pass --debug to save the output to readme.1stpass (main.py) and readme.finalpass (html2bbcode)
|
|
3
|
+
# for further debugging, you can convert the markdown file to AST using md2ast.py. Remember to load the plugin(s) you want to test.
|
|
4
|
+
|
|
5
|
+
#standard library
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# mistune
|
|
10
|
+
import mistune
|
|
11
|
+
from mistune.plugins.formatting import strikethrough, mark, superscript, subscript, insert
|
|
12
|
+
from mistune.plugins.table import table, table_in_list
|
|
13
|
+
from mistune.plugins.footnotes import footnotes
|
|
14
|
+
from mistune.plugins.task_lists import task_lists
|
|
15
|
+
from mistune.plugins.def_list import def_list
|
|
16
|
+
from mistune.plugins.abbr import abbr
|
|
17
|
+
from mistune.plugins.spoiler import spoiler
|
|
18
|
+
|
|
19
|
+
# local
|
|
20
|
+
from .renderers.bbcode import BBCodeRenderer
|
|
21
|
+
from .html2bbcode import process_html
|
|
22
|
+
|
|
23
|
+
def convert_markdown_to_bbcode(markdown_text, domain):
|
|
24
|
+
# Create a Markdown parser instance using the custom BBCode renderer
|
|
25
|
+
markdown_parser = mistune.create_markdown(renderer=BBCodeRenderer(domain=domain), plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list])
|
|
26
|
+
|
|
27
|
+
# Convert Markdown text to BBCode
|
|
28
|
+
return markdown_parser(markdown_text)
|
|
29
|
+
|
|
30
|
+
def process_readme(markdown_text, domain, debug=False):
|
|
31
|
+
# Convert Markdown to BBCode
|
|
32
|
+
bbcode_text = convert_markdown_to_bbcode(markdown_text, domain)
|
|
33
|
+
|
|
34
|
+
# If debug mode, save intermediate BBCode
|
|
35
|
+
if debug:
|
|
36
|
+
with open('readme.1stpass', 'w', encoding='utf-8') as file:
|
|
37
|
+
file.write(bbcode_text)
|
|
38
|
+
|
|
39
|
+
# Convert BBCode formatted as HTML to final BBCode
|
|
40
|
+
final_bbcode = process_html(bbcode_text, debug, 'readme.finalpass')
|
|
41
|
+
|
|
42
|
+
return final_bbcode
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
parser = argparse.ArgumentParser(description='Convert Markdown file to BBCode with HTML processing.')
|
|
46
|
+
parser.add_argument('input', help='Input Markdown file path')
|
|
47
|
+
parser.add_argument('--domain', help='Domain to prepend to relative URLs')
|
|
48
|
+
parser.add_argument('--debug', action='store_true', help='Output intermediate results to files for debugging')
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
if args.input == '-':
|
|
52
|
+
# Read Markdown content from stdin
|
|
53
|
+
markdown_text = sys.stdin.read()
|
|
54
|
+
else:
|
|
55
|
+
with open(args.input, 'r', encoding='utf-8') as md_file:
|
|
56
|
+
markdown_text = md_file.read()
|
|
57
|
+
|
|
58
|
+
# Process the readme and get the final BBCode
|
|
59
|
+
final_bbcode = process_readme(markdown_text, args.domain, args.debug)
|
|
60
|
+
|
|
61
|
+
# Optionally, print final BBCode to console
|
|
62
|
+
if not args.debug:
|
|
63
|
+
print(final_bbcode)
|
|
64
|
+
|
|
65
|
+
if __name__ == '__main__':
|
|
66
|
+
main()
|
md2bbcode/md2ast.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# this is for debugging the custom mistune renderer bbcode.py
|
|
2
|
+
import argparse
|
|
3
|
+
import mistune
|
|
4
|
+
import json # Import the json module for serialization
|
|
5
|
+
from mistune.plugins.formatting import strikethrough, mark, superscript, subscript, insert
|
|
6
|
+
from mistune.plugins.table import table, table_in_list
|
|
7
|
+
from mistune.plugins.footnotes import footnotes
|
|
8
|
+
from mistune.plugins.task_lists import task_lists
|
|
9
|
+
from mistune.plugins.def_list import def_list
|
|
10
|
+
from mistune.plugins.abbr import abbr
|
|
11
|
+
from mistune.plugins.spoiler import spoiler
|
|
12
|
+
|
|
13
|
+
def convert_markdown_to_ast(input_filepath, output_filepath):
|
|
14
|
+
# Initialize Markdown parser with no renderer to produce an AST
|
|
15
|
+
markdown_parser = mistune.create_markdown(renderer=None, plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list])
|
|
16
|
+
|
|
17
|
+
# Read the input Markdown file
|
|
18
|
+
with open(input_filepath, 'r', encoding='utf-8') as md_file:
|
|
19
|
+
markdown_text = md_file.read()
|
|
20
|
+
|
|
21
|
+
# Convert Markdown text to AST
|
|
22
|
+
ast_text = markdown_parser(markdown_text)
|
|
23
|
+
|
|
24
|
+
# Serialize the AST to a JSON string
|
|
25
|
+
ast_json = json.dumps(ast_text, indent=4)
|
|
26
|
+
|
|
27
|
+
# Write the output AST to a new file in JSON format
|
|
28
|
+
with open(output_filepath, 'w', encoding='utf-8') as ast_file:
|
|
29
|
+
ast_file.write(ast_json)
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
# Create argument parser
|
|
33
|
+
parser = argparse.ArgumentParser(description='Convert Markdown file to AST file (JSON format).')
|
|
34
|
+
# Add arguments
|
|
35
|
+
parser.add_argument('input', help='Input Markdown file path')
|
|
36
|
+
parser.add_argument('output', help='Output AST file path (JSON format)')
|
|
37
|
+
# Parse arguments
|
|
38
|
+
args = parser.parse_args()
|
|
39
|
+
|
|
40
|
+
# Convert the Markdown to AST using the provided paths
|
|
41
|
+
convert_markdown_to_ast(args.input, args.output)
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from mistune.core import BaseRenderer
|
|
2
|
+
from mistune.util import escape as escape_text, striptags, safe_entity
|
|
3
|
+
from urllib.parse import urljoin, urlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BBCodeRenderer(BaseRenderer):
|
|
7
|
+
"""A renderer for converting Markdown to BBCode."""
|
|
8
|
+
_escape: bool
|
|
9
|
+
NAME = 'bbcode'
|
|
10
|
+
|
|
11
|
+
def __init__(self, escape=False, domain=None):
|
|
12
|
+
super(BBCodeRenderer, self).__init__()
|
|
13
|
+
self._escape = escape
|
|
14
|
+
self.domain = domain
|
|
15
|
+
|
|
16
|
+
def render_token(self, token, state):
|
|
17
|
+
func = self._get_method(token['type'])
|
|
18
|
+
attrs = token.get('attrs')
|
|
19
|
+
|
|
20
|
+
if 'raw' in token:
|
|
21
|
+
text = token['raw']
|
|
22
|
+
elif 'children' in token:
|
|
23
|
+
text = self.render_tokens(token['children'], state)
|
|
24
|
+
else:
|
|
25
|
+
if attrs:
|
|
26
|
+
return func(**attrs)
|
|
27
|
+
else:
|
|
28
|
+
return func()
|
|
29
|
+
if attrs:
|
|
30
|
+
return func(text, **attrs)
|
|
31
|
+
else:
|
|
32
|
+
return func(text)
|
|
33
|
+
|
|
34
|
+
def safe_url(self, url: str) -> str:
|
|
35
|
+
# Simple URL sanitization
|
|
36
|
+
if url.startswith(('javascript:', 'vbscript:', 'data:')):
|
|
37
|
+
return '#harmful-link'
|
|
38
|
+
# Check if the URL is absolute by looking for a netloc part in the URL
|
|
39
|
+
if not urlparse(url).netloc:
|
|
40
|
+
url = urljoin(self.domain, url)
|
|
41
|
+
return url
|
|
42
|
+
|
|
43
|
+
def text(self, text: str) -> str:
|
|
44
|
+
if self._escape:
|
|
45
|
+
return escape_text(text)
|
|
46
|
+
return text
|
|
47
|
+
|
|
48
|
+
def emphasis(self, text: str) -> str:
|
|
49
|
+
return '[i]' + text + '[/i]'
|
|
50
|
+
|
|
51
|
+
def strong(self, text: str) -> str:
|
|
52
|
+
return '[b]' + text + '[/b]'
|
|
53
|
+
|
|
54
|
+
def link(self, text: str, url: str, title=None) -> str:
|
|
55
|
+
return '[url=' + self.safe_url(url) + ']' + text + '[/url]'
|
|
56
|
+
|
|
57
|
+
def image(self, text: str, url: str, title=None) -> str:
|
|
58
|
+
alt_text = f' alt="{text}"' if text else ''
|
|
59
|
+
return f'[img{alt_text}]' + self.safe_url(url) + '[/img]'
|
|
60
|
+
|
|
61
|
+
def codespan(self, text: str) -> str:
|
|
62
|
+
return '[icode]' + text + '[/icode]'
|
|
63
|
+
|
|
64
|
+
def linebreak(self) -> str:
|
|
65
|
+
return '\n'
|
|
66
|
+
|
|
67
|
+
def softbreak(self) -> str:
|
|
68
|
+
return ''
|
|
69
|
+
|
|
70
|
+
def inline_html(self, html: str) -> str:
|
|
71
|
+
if self._escape:
|
|
72
|
+
return escape_text(html)
|
|
73
|
+
return html
|
|
74
|
+
|
|
75
|
+
def paragraph(self, text: str) -> str:
|
|
76
|
+
return text + '\n\n'
|
|
77
|
+
|
|
78
|
+
def heading(self, text: str, level: int, **attrs) -> str:
|
|
79
|
+
if 1 <= level <= 3:
|
|
80
|
+
return f"[HEADING={level}]{text}[/HEADING]\n"
|
|
81
|
+
else:
|
|
82
|
+
# Handle cases where level is outside 1-3
|
|
83
|
+
return f"[HEADING=3]{text}[/HEADING]\n"
|
|
84
|
+
|
|
85
|
+
def blank_line(self) -> str:
|
|
86
|
+
return ''
|
|
87
|
+
|
|
88
|
+
def thematic_break(self) -> str:
|
|
89
|
+
return '[hr][/hr]\n'
|
|
90
|
+
|
|
91
|
+
def block_text(self, text: str) -> str:
|
|
92
|
+
return text
|
|
93
|
+
|
|
94
|
+
def block_code(self, code: str, **attrs) -> str:
|
|
95
|
+
# Renders blocks of code using the language specified in Markdown
|
|
96
|
+
special_cases = {
|
|
97
|
+
'plaintext': None # Default [CODE]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if 'info' in attrs:
|
|
101
|
+
lang_info = safe_entity(attrs['info'].strip())
|
|
102
|
+
lang = lang_info.split(None, 1)[0].lower()
|
|
103
|
+
# Check if the language needs special handling
|
|
104
|
+
bbcode_lang = special_cases.get(lang, lang) # Use the special case if it exists, otherwise use lang as is
|
|
105
|
+
if bbcode_lang:
|
|
106
|
+
return f"[CODE={bbcode_lang}]{escape_text(code)}[/CODE]\n\n"
|
|
107
|
+
else:
|
|
108
|
+
return f"[CODE]{escape_text(code)}[/CODE]\n\n"
|
|
109
|
+
else:
|
|
110
|
+
# No language specified, render with a generic [CODE] tag
|
|
111
|
+
return f"[CODE]{escape_text(code)}[/CODE]\n\n"
|
|
112
|
+
|
|
113
|
+
def block_quote(self, text: str) -> str:
|
|
114
|
+
return '[QUOTE]\n' + text + '[/QUOTE]\n'
|
|
115
|
+
|
|
116
|
+
def block_html(self, html: str) -> str:
|
|
117
|
+
if self._escape:
|
|
118
|
+
return '<p>' + escape_text(html.strip()) + '</p>\n'
|
|
119
|
+
return html + '\n'
|
|
120
|
+
|
|
121
|
+
def block_error(self, text: str) -> str:
|
|
122
|
+
return '[color=red][icode]' + text + '[/icode][/color]\n'
|
|
123
|
+
|
|
124
|
+
def list(self, text: str, ordered: bool, **attrs) -> str:
|
|
125
|
+
tag = 'list' if not ordered else 'list=1'
|
|
126
|
+
return '[{}]'.format(tag) + text + '[/list]\n'
|
|
127
|
+
|
|
128
|
+
def list_item(self, text: str) -> str:
|
|
129
|
+
return '[*]' + text + '\n'
|
|
130
|
+
|
|
131
|
+
def strikethrough(self, text: str) -> str:
|
|
132
|
+
return '[s]' + text + '[/s]'
|
|
133
|
+
|
|
134
|
+
def mark(self, text: str) -> str:
|
|
135
|
+
# Simulate the mark effect with a background color in BBCode
|
|
136
|
+
return '[mark]' + text + '[/mark]'
|
|
137
|
+
|
|
138
|
+
def insert(self, text: str) -> str:
|
|
139
|
+
# Use underline to represent insertion
|
|
140
|
+
return '[u]' + text + '[/u]'
|
|
141
|
+
|
|
142
|
+
def superscript(self, text: str) -> str:
|
|
143
|
+
return '[sup]' + text + '[/sup]'
|
|
144
|
+
|
|
145
|
+
def subscript(self, text: str) -> str:
|
|
146
|
+
return '[sub]' + text + '[/sub]'
|
|
147
|
+
|
|
148
|
+
def inline_spoiler(self, text: str) -> str:
|
|
149
|
+
return '[ISPOILER]' + text + '[/ISPOILER]'
|
|
150
|
+
|
|
151
|
+
def block_spoiler(self, text: str) -> str:
|
|
152
|
+
return '[SPOILER]\n' + text + '\n[/SPOILER]'
|
|
153
|
+
|
|
154
|
+
def footnote_ref(self, key: str, index: int):
|
|
155
|
+
# Use superscript for the footnote reference
|
|
156
|
+
return f'[sup][u][JUMPTO=fn-{index}]{index}[/JUMPTO][/u][/sup]'
|
|
157
|
+
|
|
158
|
+
def footnotes(self, text: str):
|
|
159
|
+
# Optionally wrap all footnotes in a specific section if needed
|
|
160
|
+
return '[b]Footnotes:[/b]\n' + text
|
|
161
|
+
|
|
162
|
+
def footnote_item(self, text: str, key: str, index: int):
|
|
163
|
+
# Define the footnote with an anchor at the end of the document
|
|
164
|
+
return f'[ANAME=fn-{index}]{index}[/ANAME]. {text}'
|
|
165
|
+
|
|
166
|
+
def table(self, children, **attrs):
|
|
167
|
+
# Starting with a full-width table by default if not specified
|
|
168
|
+
# width = attrs.get('width', '100%') # comment out until XF 2.3
|
|
169
|
+
# return f'[TABLE width="{width}"]\n' + children + '[/TABLE]\n' # comment out until XF 2.3
|
|
170
|
+
return '[TABLE]\n' + children + '[/TABLE]\n'
|
|
171
|
+
|
|
172
|
+
def table_head(self, children, **attrs):
|
|
173
|
+
return '[TR]\n' + children + '[/TR]\n'
|
|
174
|
+
|
|
175
|
+
def table_body(self, children, **attrs):
|
|
176
|
+
return children
|
|
177
|
+
|
|
178
|
+
def table_row(self, children, **attrs):
|
|
179
|
+
return '[TR]\n' + children + '[/TR]\n'
|
|
180
|
+
|
|
181
|
+
def table_cell(self, text, align=None, head=False, **attrs):
|
|
182
|
+
# BBCode does not support direct cell alignment,
|
|
183
|
+
# use [LEFT], [CENTER], or [RIGHT] tags
|
|
184
|
+
|
|
185
|
+
# Use th for header cells and td for normal cells
|
|
186
|
+
tag = 'TH' if head else 'TD'
|
|
187
|
+
|
|
188
|
+
# Initialize alignment tags
|
|
189
|
+
alignment_start = ''
|
|
190
|
+
alignment_end = ''
|
|
191
|
+
|
|
192
|
+
if align == 'center':
|
|
193
|
+
alignment_start = '[CENTER]'
|
|
194
|
+
alignment_end = '[/CENTER]'
|
|
195
|
+
elif align == 'right':
|
|
196
|
+
alignment_start = '[RIGHT]'
|
|
197
|
+
alignment_end = '[/RIGHT]'
|
|
198
|
+
elif align == 'left':
|
|
199
|
+
alignment_start = '[LEFT]'
|
|
200
|
+
alignment_end = '[/LEFT]'
|
|
201
|
+
|
|
202
|
+
return f'[{tag}]{alignment_start}{text}{alignment_end}[/{tag}]\n'
|
|
203
|
+
|
|
204
|
+
def task_list_item(self, text: str, checked: bool = False) -> str:
|
|
205
|
+
# Using emojis to represent the checkbox
|
|
206
|
+
checkbox_emoji = '🗹' if checked else '☐'
|
|
207
|
+
return checkbox_emoji + ' ' + text + '\n'
|
|
208
|
+
|
|
209
|
+
def def_list(self, text: str) -> str:
|
|
210
|
+
# No specific BBCode tag for <dl>, so we just use the plain text grouping
|
|
211
|
+
return '\n' + text + '\n'
|
|
212
|
+
|
|
213
|
+
def def_list_head(self, text: str) -> str:
|
|
214
|
+
return '[b]' + text + '[/b]' + ' ' + ':' + '\n'
|
|
215
|
+
|
|
216
|
+
def def_list_item(self, text: str) -> str:
|
|
217
|
+
return '[INDENT]' + text + '[/INDENT]\n'
|
|
218
|
+
|
|
219
|
+
def abbr(self, text: str, title: str) -> str:
|
|
220
|
+
if title:
|
|
221
|
+
return f'[abbr={title}]{text}[/abbr]'
|
|
222
|
+
return text
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: md2bbcode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert Markdown to BBCode using a custom Mistune renderer.
|
|
5
|
+
Project-URL: Homepage, https://github.com/RedGuides/md2bbcode
|
|
6
|
+
Project-URL: Repository, https://github.com/RedGuides/md2bbcode.git
|
|
7
|
+
Project-URL: Issues, https://github.com/RedGuides/md2bbcode/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/RedGuides/md2bbcode#readme
|
|
9
|
+
Author-email: Redbot <ask@redguides.com>
|
|
10
|
+
License-Expression: GPL-3.0-or-later
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
18
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Dist: beautifulsoup4
|
|
21
|
+
Requires-Dist: mistune>=3.0.2
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
# md2bbcode
|
|
27
|
+
**A wrapper and plugin for [Mistune](https://github.com/lepture/mistune).** It converts GitHub-flavored Markdown to Xenforo-flavored BBCode. Custom BBCodes made for RedGuides are included in `bb_codes.xml`.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
You can install md2bbcode using pip:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install md2bbcode
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
After installation, you can use md2bbcode from the command line:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
md2bbcode README.md
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If the markdown includes relative images or other assets, you can use the --domain flag to prepend a domain to the relative URLs:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
md2bbcode README.md --domain https://raw.githubusercontent.com/RedGuides/md2bbcode/main/
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Debug Mode
|
|
52
|
+
|
|
53
|
+
You can use the `--debug` flag to save intermediate results to files for debugging:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
md2bbcode README.md --debug
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
If you want to contribute to md2bbcode or set up a development environment, follow these steps:
|
|
62
|
+
|
|
63
|
+
1. Clone the repository:
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/RedGuides/md2bbcode.git
|
|
66
|
+
cd md2bbcode
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. Install Hatch, which is used for building and managing the project:
|
|
70
|
+
```bash
|
|
71
|
+
pip install hatch
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
3. Create a development environment and install dependencies:
|
|
75
|
+
```bash
|
|
76
|
+
hatch env create
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. Activate the development environment:
|
|
80
|
+
```bash
|
|
81
|
+
hatch shell
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### renderers/bbcode.py
|
|
85
|
+
|
|
86
|
+
The custom plugin for Mistune, which converts AST to bbcode.[^1]
|
|
87
|
+
|
|
88
|
+
[^1]: Mistune does not convert Markdown HTML to AST, hence the need for `html2bbcode`.
|
|
89
|
+
|
|
90
|
+
## Additional Tools
|
|
91
|
+
|
|
92
|
+
### html2bbcode
|
|
93
|
+
|
|
94
|
+
Converts several HTML tags typically allowed in Markdown to BBCode.[^2]
|
|
95
|
+
|
|
96
|
+
[^2]: Currently used for post-processing mistune output, but there's a better way. See inside the file for a suggestion.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
html2bbcode input_file.html
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### md2ast
|
|
103
|
+
|
|
104
|
+
For debugging Mistune's renderer, converts a Markdown file to AST (JSON format).
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
md2ast input.md output.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Features Test
|
|
111
|
+
|
|
112
|
+
Here are a few GitHub-flavored Markdown features so you can use this README.md for testing:
|
|
113
|
+
|
|
114
|
+
- **Strikethrough:** ~~This text is struck through.~~
|
|
115
|
+
- **Superscript:** This text is normal and this is <sup>superscript</sup>.
|
|
116
|
+
- **Table:**
|
|
117
|
+
|
|
118
|
+
| Syntax | Description |
|
|
119
|
+
| ----------- | ----------- |
|
|
120
|
+
| Header | Title |
|
|
121
|
+
| Paragraph | Text |
|
|
122
|
+
|
|
123
|
+
## Todo
|
|
124
|
+
|
|
125
|
+
- refactor html2bbcode
|
|
126
|
+
- update for new Xenforo 2.3 and 2.4 BBCode
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
md2bbcode/__about__.py,sha256=Pru0BlFBASFCFo7McHdohtKkUtgMPDwbGfyUZlE2_Vw,21
|
|
2
|
+
md2bbcode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
md2bbcode/eqlog.md,sha256=acMtTLnE-kfmRnVq1pZeLUmeLDHgEH6QNWUK-RE7EJU,2182
|
|
4
|
+
md2bbcode/html2bbcode.py,sha256=yONG7DMxx_QBvSExGH0_viw6RnX6_PXDtAubkXvUf2c,6102
|
|
5
|
+
md2bbcode/main.py,sha256=g5Lp_HO6AsT6aYX2g7aUchhNSzyubjyGqUvAuViUR4c,2757
|
|
6
|
+
md2bbcode/md2ast.py,sha256=gsmDTuDyTL7Q25bX2GhDR2TkMg1y3-mte4PnUGYpKk0,1888
|
|
7
|
+
md2bbcode/renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
md2bbcode/renderers/bbcode.py,sha256=425aNzLiGvJHlKAlK78jL4HiPtin18sg5oGzzkjK4pY,7807
|
|
9
|
+
md2bbcode-0.1.0.dist-info/METADATA,sha256=OJHafV0w6s5W_kExIg500WGB5mFGBFB_6Bm0ETMcEho,3454
|
|
10
|
+
md2bbcode-0.1.0.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
|
|
11
|
+
md2bbcode-0.1.0.dist-info/entry_points.txt,sha256=JUMQnuUEsZ8Fy5vx5O7hYWiD7QWSmByfTFIB1aD6Y9Q,122
|
|
12
|
+
md2bbcode-0.1.0.dist-info/licenses/LICENSE.txt,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
13
|
+
md2bbcode-0.1.0.dist-info/RECORD,,
|