Spaces:
Sleeping
Sleeping
| # GFM table, https://github.github.com/gfm/#tables-extension- | |
| from __future__ import annotations | |
| import re | |
| from ..common.utils import charStrAt, isStrSpace | |
| from .state_block import StateBlock | |
| headerLineRe = re.compile(r"^:?-+:?$") | |
| enclosingPipesRe = re.compile(r"^\||\|$") | |
| def getLine(state: StateBlock, line: int) -> str: | |
| pos = state.bMarks[line] + state.tShift[line] | |
| maximum = state.eMarks[line] | |
| # return state.src.substr(pos, max - pos) | |
| return state.src[pos:maximum] | |
| def escapedSplit(string: str) -> list[str]: | |
| result: list[str] = [] | |
| pos = 0 | |
| max = len(string) | |
| isEscaped = False | |
| lastPos = 0 | |
| current = "" | |
| ch = charStrAt(string, pos) | |
| while pos < max: | |
| if ch == "|": | |
| if not isEscaped: | |
| # pipe separating cells, '|' | |
| result.append(current + string[lastPos:pos]) | |
| current = "" | |
| lastPos = pos + 1 | |
| else: | |
| # escaped pipe, '\|' | |
| current += string[lastPos : pos - 1] | |
| lastPos = pos | |
| isEscaped = ch == "\\" | |
| pos += 1 | |
| ch = charStrAt(string, pos) | |
| result.append(current + string[lastPos:]) | |
| return result | |
| def table(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: | |
| tbodyLines = None | |
| # should have at least two lines | |
| if startLine + 2 > endLine: | |
| return False | |
| nextLine = startLine + 1 | |
| if state.sCount[nextLine] < state.blkIndent: | |
| return False | |
| if state.is_code_block(nextLine): | |
| return False | |
| # first character of the second line should be '|', '-', ':', | |
| # and no other characters are allowed but spaces; | |
| # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp | |
| pos = state.bMarks[nextLine] + state.tShift[nextLine] | |
| if pos >= state.eMarks[nextLine]: | |
| return False | |
| first_ch = state.src[pos] | |
| pos += 1 | |
| if first_ch not in ("|", "-", ":"): | |
| return False | |
| if pos >= state.eMarks[nextLine]: | |
| return False | |
| second_ch = state.src[pos] | |
| pos += 1 | |
| if second_ch not in ("|", "-", ":") and not isStrSpace(second_ch): | |
| return False | |
| # if first character is '-', then second character must not be a space | |
| # (due to parsing ambiguity with list) | |
| if first_ch == "-" and isStrSpace(second_ch): | |
| return False | |
| while pos < state.eMarks[nextLine]: | |
| ch = state.src[pos] | |
| if ch not in ("|", "-", ":") and not isStrSpace(ch): | |
| return False | |
| pos += 1 | |
| lineText = getLine(state, startLine + 1) | |
| columns = lineText.split("|") | |
| aligns = [] | |
| for i in range(len(columns)): | |
| t = columns[i].strip() | |
| if not t: | |
| # allow empty columns before and after table, but not in between columns; | |
| # e.g. allow ` |---| `, disallow ` ---||--- ` | |
| if i == 0 or i == len(columns) - 1: | |
| continue | |
| else: | |
| return False | |
| if not headerLineRe.search(t): | |
| return False | |
| if charStrAt(t, len(t) - 1) == ":": | |
| aligns.append("center" if charStrAt(t, 0) == ":" else "right") | |
| elif charStrAt(t, 0) == ":": | |
| aligns.append("left") | |
| else: | |
| aligns.append("") | |
| lineText = getLine(state, startLine).strip() | |
| if "|" not in lineText: | |
| return False | |
| if state.is_code_block(startLine): | |
| return False | |
| columns = escapedSplit(lineText) | |
| if columns and columns[0] == "": | |
| columns.pop(0) | |
| if columns and columns[-1] == "": | |
| columns.pop() | |
| # header row will define an amount of columns in the entire table, | |
| # and align row should be exactly the same (the rest of the rows can differ) | |
| columnCount = len(columns) | |
| if columnCount == 0 or columnCount != len(aligns): | |
| return False | |
| if silent: | |
| return True | |
| oldParentType = state.parentType | |
| state.parentType = "table" | |
| # use 'blockquote' lists for termination because it's | |
| # the most similar to tables | |
| terminatorRules = state.md.block.ruler.getRules("blockquote") | |
| token = state.push("table_open", "table", 1) | |
| token.map = tableLines = [startLine, 0] | |
| token = state.push("thead_open", "thead", 1) | |
| token.map = [startLine, startLine + 1] | |
| token = state.push("tr_open", "tr", 1) | |
| token.map = [startLine, startLine + 1] | |
| for i in range(len(columns)): | |
| token = state.push("th_open", "th", 1) | |
| if aligns[i]: | |
| token.attrs = {"style": "text-align:" + aligns[i]} | |
| token = state.push("inline", "", 0) | |
| # note in markdown-it this map was removed in v12.0.0 however, we keep it, | |
| # since it is helpful to propagate to children tokens | |
| token.map = [startLine, startLine + 1] | |
| token.content = columns[i].strip() | |
| token.children = [] | |
| token = state.push("th_close", "th", -1) | |
| token = state.push("tr_close", "tr", -1) | |
| token = state.push("thead_close", "thead", -1) | |
| nextLine = startLine + 2 | |
| while nextLine < endLine: | |
| if state.sCount[nextLine] < state.blkIndent: | |
| break | |
| terminate = False | |
| for i in range(len(terminatorRules)): | |
| if terminatorRules[i](state, nextLine, endLine, True): | |
| terminate = True | |
| break | |
| if terminate: | |
| break | |
| lineText = getLine(state, nextLine).strip() | |
| if not lineText: | |
| break | |
| if state.is_code_block(nextLine): | |
| break | |
| columns = escapedSplit(lineText) | |
| if columns and columns[0] == "": | |
| columns.pop(0) | |
| if columns and columns[-1] == "": | |
| columns.pop() | |
| if nextLine == startLine + 2: | |
| token = state.push("tbody_open", "tbody", 1) | |
| token.map = tbodyLines = [startLine + 2, 0] | |
| token = state.push("tr_open", "tr", 1) | |
| token.map = [nextLine, nextLine + 1] | |
| for i in range(columnCount): | |
| token = state.push("td_open", "td", 1) | |
| if aligns[i]: | |
| token.attrs = {"style": "text-align:" + aligns[i]} | |
| token = state.push("inline", "", 0) | |
| # note in markdown-it this map was removed in v12.0.0 however, we keep it, | |
| # since it is helpful to propagate to children tokens | |
| token.map = [nextLine, nextLine + 1] | |
| try: | |
| token.content = columns[i].strip() if columns[i] else "" | |
| except IndexError: | |
| token.content = "" | |
| token.children = [] | |
| token = state.push("td_close", "td", -1) | |
| token = state.push("tr_close", "tr", -1) | |
| nextLine += 1 | |
| if tbodyLines: | |
| token = state.push("tbody_close", "tbody", -1) | |
| tbodyLines[1] = nextLine | |
| token = state.push("table_close", "table", -1) | |
| tableLines[1] = nextLine | |
| state.parentType = oldParentType | |
| state.line = nextLine | |
| return True | |