Source code for markdown_it.rules_block.reference

import logging

from ..common.utils import isSpace, normalizeReference, charCodeAt
from .state_block import StateBlock


LOGGER = logging.getLogger(__name__)


[docs]def reference(state: StateBlock, startLine, _endLine, silent): LOGGER.debug( "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent ) lines = 0 pos = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] nextLine = startLine + 1 # if it's indented more than 3 spaces, it should be a code block if state.sCount[startLine] - state.blkIndent >= 4: return False if state.srcCharCode[pos] != 0x5B: # /* [ */ return False # Simple check to quickly interrupt scan on [link](url) at the start of line. # Can be useful on practice: https:#github.com/markdown-it/markdown-it/issues/54 while pos < maximum: # /* ] */ /* \ */ /* : */ if state.srcCharCode[pos] == 0x5D and state.srcCharCode[pos - 1] != 0x5C: if pos + 1 == maximum: return False if state.srcCharCode[pos + 1] != 0x3A: return False break pos += 1 endLine = state.lineMax # jump line-by-line until empty one or EOF terminatorRules = state.md.block.ruler.getRules("reference") oldParentType = state.parentType state.parentType = "reference" while nextLine < endLine and not state.isEmpty(nextLine): # this would be a code block normally, but after paragraph # it's considered a lazy continuation regardless of what's there if state.sCount[nextLine] - state.blkIndent > 3: nextLine += 1 continue # quirk for blockquotes, this line should already be checked by that rule if state.sCount[nextLine] < 0: nextLine += 1 continue # Some tags can terminate paragraph without empty line. terminate = False for terminatorRule in terminatorRules: if terminatorRule(state, nextLine, endLine, True): terminate = True break if terminate: break nextLine += 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 */ lines += 1 elif ch == 0x5C: # /* \ */ pos += 1 if pos < maximum and charCodeAt(string, pos) == 0x0A: lines += 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: lines += 1 elif isSpace(ch): pass else: break pos += 1 # [label]: destination 'title' # ^^^^^^^^^^^ parse this res = state.md.helpers.parseLinkDestination(string, pos, maximum) if not res.ok: return False href = state.md.normalizeLink(res.str) if not state.md.validateLink(href): return False pos = res.pos lines += res.lines # save cursor state, we could require to rollback later destEndPos = pos destEndLineNo = lines # [label]: destination 'title' # ^^^ skipping those spaces start = pos while pos < maximum: ch = charCodeAt(string, pos) if ch == 0x0A: lines += 1 elif isSpace(ch): pass else: break pos += 1 # [label]: destination 'title' # ^^^^^^^ parse this res = state.md.helpers.parseLinkTitle(string, pos, maximum) if pos < maximum and start != pos and res.ok: title = res.str pos = res.pos lines += res.lines else: title = "" pos = destEndPos lines = 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: if 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 lines = 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 = startLine + lines + 1 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], } ) state.parentType = oldParentType return True