2023-01-25 19:58:39 +01:00
|
|
|
from collections.abc import MutableMapping, Sequence
|
|
|
|
from frozendict import frozendict # type: ignore[attr-defined]
|
2023-01-25 20:11:56 +01:00
|
|
|
from typing import Any, Optional
|
2023-01-25 19:58:39 +01:00
|
|
|
|
|
|
|
import markdown_it
|
|
|
|
from markdown_it.token import Token
|
|
|
|
from markdown_it.utils import OptionsDict
|
|
|
|
from xml.sax.saxutils import escape, quoteattr
|
|
|
|
|
2023-01-25 20:19:01 +01:00
|
|
|
from .md import Renderer
|
|
|
|
|
2023-01-25 19:29:02 +01:00
|
|
|
_xml_id_translate_table = {
|
|
|
|
ord('*'): ord('_'),
|
|
|
|
ord('<'): ord('_'),
|
|
|
|
ord(' '): ord('_'),
|
|
|
|
ord('>'): ord('_'),
|
|
|
|
ord('['): ord('_'),
|
|
|
|
ord(']'): ord('_'),
|
|
|
|
ord(':'): ord('_'),
|
|
|
|
ord('"'): ord('_'),
|
|
|
|
}
|
|
|
|
def make_xml_id(s: str) -> str:
|
|
|
|
return s.translate(_xml_id_translate_table)
|
2023-01-25 19:58:39 +01:00
|
|
|
|
2023-01-25 20:19:01 +01:00
|
|
|
class DocBookRenderer(Renderer):
|
2023-01-25 19:58:39 +01:00
|
|
|
__output__ = "docbook"
|
2023-01-25 20:11:56 +01:00
|
|
|
def render(self, tokens: Sequence[Token], options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
assert '-link-tag-stack' not in env
|
|
|
|
env['-link-tag-stack'] = []
|
|
|
|
assert '-deflist-stack' not in env
|
|
|
|
env['-deflist-stack'] = []
|
2023-01-25 20:19:01 +01:00
|
|
|
return super().render(tokens, options, env)
|
2023-01-25 20:11:56 +01:00
|
|
|
def renderInline(self, tokens: Sequence[Token], options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
# HACK to support docbook links and xrefs. link handling is only necessary because the docbook
|
|
|
|
# manpage stylesheet converts - in urls to a mathematical minus, which may be somewhat incorrect.
|
|
|
|
for i, token in enumerate(tokens):
|
|
|
|
if token.type != 'link_open':
|
|
|
|
continue
|
|
|
|
token.tag = 'link'
|
|
|
|
# turn [](#foo) into xrefs
|
2023-01-25 20:11:56 +01:00
|
|
|
if token.attrs['href'][0:1] == '#' and tokens[i + 1].type == 'link_close': # type: ignore[index]
|
2023-01-25 19:58:39 +01:00
|
|
|
token.tag = "xref"
|
|
|
|
# turn <x> into links without contents
|
|
|
|
if tokens[i + 1].type == 'text' and tokens[i + 1].content == token.attrs['href']:
|
|
|
|
tokens[i + 1].content = ''
|
|
|
|
|
2023-01-25 20:19:01 +01:00
|
|
|
return super().renderInline(tokens, options, env)
|
2023-01-25 19:58:39 +01:00
|
|
|
|
2023-01-25 20:11:56 +01:00
|
|
|
def text(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return escape(token.content)
|
2023-01-25 20:11:56 +01:00
|
|
|
def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def hardbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<literallayout>\n</literallayout>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def softbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
# should check options.breaks() and emit hard break if so
|
|
|
|
return "\n"
|
2023-01-25 20:11:56 +01:00
|
|
|
def code_inline(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return f"<literal>{escape(token.content)}</literal>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def code_block(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return f"<programlisting>{escape(token.content)}</programlisting>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def link_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
env['-link-tag-stack'].append(token.tag)
|
|
|
|
(attr, start) = ('linkend', 1) if token.attrs['href'][0] == '#' else ('xlink:href', 0)
|
|
|
|
return f"<{token.tag} {attr}={quoteattr(token.attrs['href'][start:])}>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def link_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return f"</{env['-link-tag-stack'].pop()}>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def list_item_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<listitem>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def list_item_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</listitem>\n"
|
|
|
|
# HACK open and close para for docbook change size. remove soon.
|
2023-01-25 20:11:56 +01:00
|
|
|
def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para><itemizedlist>\n"
|
2023-01-25 20:11:56 +01:00
|
|
|
def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "\n</itemizedlist></para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def em_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<emphasis>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def em_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</emphasis>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def strong_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<emphasis role=\"strong\">"
|
2023-01-25 20:11:56 +01:00
|
|
|
def strong_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</emphasis>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def fence(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
info = f" language={quoteattr(token.info)}" if token.info != "" else ""
|
|
|
|
return f"<programlisting{info}>{escape(token.content)}</programlisting>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para><blockquote>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</blockquote></para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def note_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para><note>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def note_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</note></para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def important_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para><important>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def important_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</important></para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def warning_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "<para><warning>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def warning_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</warning></para>"
|
|
|
|
# markdown-it emits tokens based on the html syntax tree, but docbook is
|
|
|
|
# slightly different. html has <dl>{<dt/>{<dd/>}}</dl>,
|
|
|
|
# docbook has <variablelist>{<varlistentry><term/><listitem/></varlistentry>}<variablelist>
|
|
|
|
# we have to reject multiple definitions for the same term for time being.
|
2023-01-25 20:11:56 +01:00
|
|
|
def dl_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
env['-deflist-stack'].append({})
|
|
|
|
return "<para><variablelist>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def dl_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
env['-deflist-stack'].pop()
|
|
|
|
return "</variablelist></para>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def dt_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
env['-deflist-stack'][-1]['has-dd'] = False
|
|
|
|
return "<varlistentry><term>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def dt_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</term>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def dd_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
if env['-deflist-stack'][-1]['has-dd']:
|
|
|
|
raise Exception("multiple definitions per term not supported")
|
|
|
|
env['-deflist-stack'][-1]['has-dd'] = True
|
|
|
|
return "<listitem>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def dd_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
return "</listitem></varlistentry>"
|
2023-01-25 20:11:56 +01:00
|
|
|
def myst_role(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
|
|
|
env: MutableMapping[str, Any]) -> str:
|
2023-01-25 19:58:39 +01:00
|
|
|
if token.meta['name'] == 'command':
|
|
|
|
return f"<command>{escape(token.content)}</command>"
|
|
|
|
if token.meta['name'] == 'file':
|
|
|
|
return f"<filename>{escape(token.content)}</filename>"
|
|
|
|
if token.meta['name'] == 'var':
|
|
|
|
return f"<varname>{escape(token.content)}</varname>"
|
|
|
|
if token.meta['name'] == 'env':
|
|
|
|
return f"<envar>{escape(token.content)}</envar>"
|
|
|
|
if token.meta['name'] == 'option':
|
|
|
|
return f"<option>{escape(token.content)}</option>"
|
|
|
|
if token.meta['name'] == 'manpage':
|
|
|
|
[page, section] = [ s.strip() for s in token.content.rsplit('(', 1) ]
|
|
|
|
section = section[:-1]
|
|
|
|
man = f"{page}({section})"
|
|
|
|
title = f"<refentrytitle>{escape(page)}</refentrytitle>"
|
|
|
|
vol = f"<manvolnum>{escape(section)}</manvolnum>"
|
|
|
|
ref = f"<citerefentry>{title}{vol}</citerefentry>"
|
|
|
|
if man in env['manpage_urls']:
|
|
|
|
return f"<link xlink:href={quoteattr(env['manpage_urls'][man])}>{ref}</link>"
|
|
|
|
else:
|
|
|
|
return ref
|
|
|
|
raise NotImplementedError("md node not supported yet", token)
|