File size: 6,983 Bytes
29f8576 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | 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]
|