link.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Process [link](<to> "stuff")
  2. from ..common.utils import isStrSpace, normalizeReference
  3. from .state_inline import StateInline
  4. def link(state: StateInline, silent: bool) -> bool:
  5. href = ""
  6. title = ""
  7. label = None
  8. oldPos = state.pos
  9. maximum = state.posMax
  10. start = state.pos
  11. parseReference = True
  12. if state.src[state.pos] != "[":
  13. return False
  14. labelStart = state.pos + 1
  15. labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, True)
  16. # parser failed to find ']', so it's not a valid link
  17. if labelEnd < 0:
  18. return False
  19. pos = labelEnd + 1
  20. if pos < maximum and state.src[pos] == "(":
  21. #
  22. # Inline link
  23. #
  24. # might have found a valid shortcut link, disable reference parsing
  25. parseReference = False
  26. # [link]( <href> "title" )
  27. # ^^ skipping these spaces
  28. pos += 1
  29. while pos < maximum:
  30. ch = state.src[pos]
  31. if not isStrSpace(ch) and ch != "\n":
  32. break
  33. pos += 1
  34. if pos >= maximum:
  35. return False
  36. # [link]( <href> "title" )
  37. # ^^^^^^ parsing link destination
  38. start = pos
  39. res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
  40. if res.ok:
  41. href = state.md.normalizeLink(res.str)
  42. if state.md.validateLink(href):
  43. pos = res.pos
  44. else:
  45. href = ""
  46. # [link]( <href> "title" )
  47. # ^^ skipping these spaces
  48. start = pos
  49. while pos < maximum:
  50. ch = state.src[pos]
  51. if not isStrSpace(ch) and ch != "\n":
  52. break
  53. pos += 1
  54. # [link]( <href> "title" )
  55. # ^^^^^^^ parsing link title
  56. res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
  57. if pos < maximum and start != pos and res.ok:
  58. title = res.str
  59. pos = res.pos
  60. # [link]( <href> "title" )
  61. # ^^ skipping these spaces
  62. while pos < maximum:
  63. ch = state.src[pos]
  64. if not isStrSpace(ch) and ch != "\n":
  65. break
  66. pos += 1
  67. if pos >= maximum or state.src[pos] != ")":
  68. # parsing a valid shortcut link failed, fallback to reference
  69. parseReference = True
  70. pos += 1
  71. if parseReference:
  72. #
  73. # Link reference
  74. #
  75. if "references" not in state.env:
  76. return False
  77. if pos < maximum and state.src[pos] == "[":
  78. start = pos + 1
  79. pos = state.md.helpers.parseLinkLabel(state, pos)
  80. if pos >= 0:
  81. label = state.src[start:pos]
  82. pos += 1
  83. else:
  84. pos = labelEnd + 1
  85. else:
  86. pos = labelEnd + 1
  87. # covers label == '' and label == undefined
  88. # (collapsed reference link and shortcut reference link respectively)
  89. if not label:
  90. label = state.src[labelStart:labelEnd]
  91. label = normalizeReference(label)
  92. ref = (
  93. state.env["references"][label] if label in state.env["references"] else None
  94. )
  95. if not ref:
  96. state.pos = oldPos
  97. return False
  98. href = ref["href"]
  99. title = ref["title"]
  100. #
  101. # We found the end of the link, and know for a fact it's a valid link
  102. # so all that's left to do is to call tokenizer.
  103. #
  104. if not silent:
  105. state.pos = labelStart
  106. state.posMax = labelEnd
  107. token = state.push("link_open", "a", 1)
  108. token.attrs = {"href": href}
  109. if title:
  110. token.attrSet("title", title)
  111. # note, this is not part of markdown-it JS, but is useful for renderers
  112. if label and state.md.options.get("store_labels", False):
  113. token.meta["label"] = label
  114. state.linkLevel += 1
  115. state.md.inline.tokenize(state)
  116. state.linkLevel -= 1
  117. token = state.push("link_close", "a", -1)
  118. state.pos = pos
  119. state.posMax = maximum
  120. return True