parse_link_title.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. """Parse link title"""
  2. from ..common.utils import charCodeAt, unescapeAll
  3. class _State:
  4. __slots__ = ("can_continue", "marker", "ok", "pos", "str")
  5. def __init__(self) -> None:
  6. self.ok = False
  7. """if `true`, this is a valid link title"""
  8. self.can_continue = False
  9. """if `true`, this link can be continued on the next line"""
  10. self.pos = 0
  11. """if `ok`, it's the position of the first character after the closing marker"""
  12. self.str = ""
  13. """if `ok`, it's the unescaped title"""
  14. self.marker = 0
  15. """expected closing marker character code"""
  16. def __str__(self) -> str:
  17. return self.str
  18. def parseLinkTitle(
  19. string: str, start: int, maximum: int, prev_state: _State | None = None
  20. ) -> _State:
  21. """Parse link title within `str` in [start, max] range,
  22. or continue previous parsing if `prev_state` is defined (equal to result of last execution).
  23. """
  24. pos = start
  25. state = _State()
  26. if prev_state is not None:
  27. # this is a continuation of a previous parseLinkTitle call on the next line,
  28. # used in reference links only
  29. state.str = prev_state.str
  30. state.marker = prev_state.marker
  31. else:
  32. if pos >= maximum:
  33. return state
  34. marker = charCodeAt(string, pos)
  35. # /* " */ /* ' */ /* ( */
  36. if marker != 0x22 and marker != 0x27 and marker != 0x28:
  37. return state
  38. start += 1
  39. pos += 1
  40. # if opening marker is "(", switch it to closing marker ")"
  41. if marker == 0x28:
  42. marker = 0x29
  43. state.marker = marker
  44. while pos < maximum:
  45. code = charCodeAt(string, pos)
  46. if code == state.marker:
  47. state.pos = pos + 1
  48. state.str += unescapeAll(string[start:pos])
  49. state.ok = True
  50. return state
  51. elif code == 0x28 and state.marker == 0x29: # /* ( */ /* ) */
  52. return state
  53. elif code == 0x5C and pos + 1 < maximum: # /* \ */
  54. pos += 1
  55. pos += 1
  56. # no closing marker found, but this link title may continue on the next line (for references)
  57. state.can_continue = True
  58. state.str += unescapeAll(string[start:pos])
  59. return state