ruby.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import re
  2. from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional
  3. from ..helpers import parse_link, parse_link_label
  4. from ..util import unikey
  5. if TYPE_CHECKING:
  6. from ..core import BaseRenderer, InlineState
  7. from ..inline_parser import InlineParser
  8. from ..markdown import Markdown
  9. RUBY_PATTERN = r"\[(?:\w+\([\w ]+\))+\]"
  10. _ruby_re = re.compile(RUBY_PATTERN)
  11. def parse_ruby(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
  12. text = m.group(0)[1:-2]
  13. items = text.split(")")
  14. tokens = []
  15. for item in items:
  16. rb, rt = item.split("(")
  17. tokens.append({"type": "ruby", "raw": rb, "attrs": {"rt": rt}})
  18. end_pos = m.end()
  19. next_match = _ruby_re.match(state.src, end_pos)
  20. if next_match:
  21. for tok in tokens:
  22. state.append_token(tok)
  23. return parse_ruby(inline, next_match, state)
  24. # repeat link logic
  25. if end_pos < len(state.src):
  26. link_pos = _parse_ruby_link(inline, state, end_pos, tokens)
  27. if link_pos:
  28. return link_pos
  29. for tok in tokens:
  30. state.append_token(tok)
  31. return end_pos
  32. def _parse_ruby_link(
  33. inline: "InlineParser", state: "InlineState", pos: int, tokens: List[Dict[str, Any]]
  34. ) -> Optional[int]:
  35. c = state.src[pos]
  36. if c == "(":
  37. # standard link [text](<url> "title")
  38. attrs, link_pos = parse_link(state.src, pos + 1)
  39. if link_pos:
  40. state.append_token(
  41. {
  42. "type": "link",
  43. "children": tokens,
  44. "attrs": attrs,
  45. }
  46. )
  47. return link_pos
  48. elif c == "[":
  49. # standard ref link [text][label]
  50. label, link_pos = parse_link_label(state.src, pos + 1)
  51. if label and link_pos:
  52. ref_links = state.env["ref_links"]
  53. key = unikey(label)
  54. env = ref_links.get(key)
  55. if env:
  56. attrs = {"url": env["url"], "title": env.get("title")}
  57. state.append_token(
  58. {
  59. "type": "link",
  60. "children": tokens,
  61. "attrs": attrs,
  62. }
  63. )
  64. else:
  65. for tok in tokens:
  66. state.append_token(tok)
  67. state.append_token(
  68. {
  69. "type": "text",
  70. "raw": "[" + label + "]",
  71. }
  72. )
  73. return link_pos
  74. return None
  75. def render_ruby(renderer: "BaseRenderer", text: str, rt: str) -> str:
  76. return "<ruby>" + text + "<rt>" + rt + "</rt></ruby>"
  77. def ruby(md: "Markdown") -> None:
  78. """A mistune plugin to support ``<ruby>`` tag. The syntax is defined
  79. at https://lepture.com/en/2022/markdown-ruby-markup:
  80. .. code-block:: text
  81. [漢字(ㄏㄢˋㄗˋ)]
  82. [漢(ㄏㄢˋ)字(ㄗˋ)]
  83. [漢字(ㄏㄢˋㄗˋ)][link]
  84. [漢字(ㄏㄢˋㄗˋ)](/url "title")
  85. [link]: /url "title"
  86. :param md: Markdown instance
  87. """
  88. md.inline.register("ruby", RUBY_PATTERN, parse_ruby, before="link")
  89. if md.renderer and md.renderer.NAME == "html":
  90. md.renderer.register("ruby", render_ruby)