| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import logging
- from ..common.utils import charCodeAt, isSpace, normalizeReference
- from .state_block import StateBlock
- LOGGER = logging.getLogger(__name__)
- def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool:
- LOGGER.debug(
- "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent
- )
- pos = state.bMarks[startLine] + state.tShift[startLine]
- maximum = state.eMarks[startLine]
- nextLine = startLine + 1
- if state.is_code_block(startLine):
- return False
- if state.src[pos] != "[":
- return False
- string = state.src[pos : maximum + 1]
- # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip()
- maximum = len(string)
- labelEnd = None
- pos = 1
- while pos < maximum:
- ch = charCodeAt(string, pos)
- if ch == 0x5B: # /* [ */
- return False
- elif ch == 0x5D: # /* ] */
- labelEnd = pos
- break
- elif ch == 0x0A: # /* \n */
- if (lineContent := getNextLine(state, nextLine)) is not None:
- string += lineContent
- maximum = len(string)
- nextLine += 1
- elif ch == 0x5C: # /* \ */
- pos += 1
- if (
- pos < maximum
- and charCodeAt(string, pos) == 0x0A
- and (lineContent := getNextLine(state, nextLine)) is not None
- ):
- string += lineContent
- maximum = len(string)
- nextLine += 1
- pos += 1
- if (
- labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A
- ): # /* : */
- return False
- # [label]: destination 'title'
- # ^^^ skip optional whitespace here
- pos = labelEnd + 2
- while pos < maximum:
- ch = charCodeAt(string, pos)
- if ch == 0x0A:
- if (lineContent := getNextLine(state, nextLine)) is not None:
- string += lineContent
- maximum = len(string)
- nextLine += 1
- elif isSpace(ch):
- pass
- else:
- break
- pos += 1
- # [label]: destination 'title'
- # ^^^^^^^^^^^ parse this
- destRes = state.md.helpers.parseLinkDestination(string, pos, maximum)
- if not destRes.ok:
- return False
- href = state.md.normalizeLink(destRes.str)
- if not state.md.validateLink(href):
- return False
- pos = destRes.pos
- # save cursor state, we could require to rollback later
- destEndPos = pos
- destEndLineNo = nextLine
- # [label]: destination 'title'
- # ^^^ skipping those spaces
- start = pos
- while pos < maximum:
- ch = charCodeAt(string, pos)
- if ch == 0x0A:
- if (lineContent := getNextLine(state, nextLine)) is not None:
- string += lineContent
- maximum = len(string)
- nextLine += 1
- elif isSpace(ch):
- pass
- else:
- break
- pos += 1
- # [label]: destination 'title'
- # ^^^^^^^ parse this
- titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None)
- while titleRes.can_continue:
- if (lineContent := getNextLine(state, nextLine)) is None:
- break
- string += lineContent
- pos = maximum
- maximum = len(string)
- nextLine += 1
- titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes)
- if pos < maximum and start != pos and titleRes.ok:
- title = titleRes.str
- pos = titleRes.pos
- else:
- title = ""
- pos = destEndPos
- nextLine = destEndLineNo
- # skip trailing spaces until the rest of the line
- while pos < maximum:
- ch = charCodeAt(string, pos)
- if not isSpace(ch):
- break
- pos += 1
- if pos < maximum and charCodeAt(string, pos) != 0x0A and title:
- # garbage at the end of the line after title,
- # but it could still be a valid reference if we roll back
- title = ""
- pos = destEndPos
- nextLine = destEndLineNo
- while pos < maximum:
- ch = charCodeAt(string, pos)
- if not isSpace(ch):
- break
- pos += 1
- if pos < maximum and charCodeAt(string, pos) != 0x0A:
- # garbage at the end of the line
- return False
- label = normalizeReference(string[1:labelEnd])
- if not label:
- # CommonMark 0.20 disallows empty labels
- return False
- # Reference can not terminate anything. This check is for safety only.
- if silent:
- return True
- if "references" not in state.env:
- state.env["references"] = {}
- state.line = nextLine
- # note, this is not part of markdown-it JS, but is useful for renderers
- if state.md.options.get("inline_definitions", False):
- token = state.push("definition", "", 0)
- token.meta = {
- "id": label,
- "title": title,
- "url": href,
- "label": string[1:labelEnd],
- }
- token.map = [startLine, state.line]
- if label not in state.env["references"]:
- state.env["references"][label] = {
- "title": title,
- "href": href,
- "map": [startLine, state.line],
- }
- else:
- state.env.setdefault("duplicate_refs", []).append(
- {
- "title": title,
- "href": href,
- "label": label,
- "map": [startLine, state.line],
- }
- )
- return True
- def getNextLine(state: StateBlock, nextLine: int) -> None | str:
- endLine = state.lineMax
- if nextLine >= endLine or state.isEmpty(nextLine):
- # empty line or end of input
- return None
- isContinuation = False
- # this would be a code block normally, but after paragraph
- # it's considered a lazy continuation regardless of what's there
- if state.is_code_block(nextLine):
- isContinuation = True
- # quirk for blockquotes, this line should already be checked by that rule
- if state.sCount[nextLine] < 0:
- isContinuation = True
- if not isContinuation:
- terminatorRules = state.md.block.ruler.getRules("reference")
- oldParentType = state.parentType
- state.parentType = "reference"
- # Some tags can terminate paragraph without empty line.
- terminate = False
- for terminatorRule in terminatorRules:
- if terminatorRule(state, nextLine, endLine, True):
- terminate = True
- break
- state.parentType = oldParentType
- if terminate:
- # terminated by another block
- return None
- pos = state.bMarks[nextLine] + state.tShift[nextLine]
- maximum = state.eMarks[nextLine]
- # max + 1 explicitly includes the newline
- return state.src[pos : maximum + 1]
|