def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool): if silent: # quirk: validation mode validates a dd block only, not a whole deflist if state.ddIndent < 0: return False return skipMarker(state, startLine) >= 0 nextLine = startLine + 1 if nextLine >= endLine: return False if state.isEmpty(nextLine): nextLine += 1 if nextLine >= endLine: return False if state.sCount[nextLine] < state.blkIndent: return False contentStart = skipMarker(state, nextLine) if contentStart < 0: return False # Start list listTokIdx = len(state.tokens) tight = True token = state.push("dl_open", "dl", 1) token.map = listLines = [startLine, 0] # Iterate list items dtLine = startLine ddLine = nextLine # One definition list can contain multiple DTs, # and one DT can be followed by multiple DDs. # # Thus, there is two loops here, and label is # needed to break out of the second one # break_outer = False while True: prevEmptyEnd = False token = state.push("dt_open", "dt", 1) token.map = [dtLine, dtLine] token = state.push("inline", "", 0) token.map = [dtLine, dtLine] token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, False).strip() token.children = [] token = state.push("dt_close", "dt", -1) while True: token = state.push("dd_open", "dd", 1) token.map = itemLines = [nextLine, 0] pos = contentStart maximum = state.eMarks[ddLine] offset = (state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])) while pos < maximum: ch = state.srcCharCode[pos] if isSpace(ch): if ch == 0x09: offset += 4 - offset % 4 else: offset += 1 else: break pos += 1 contentStart = pos oldTight = state.tight oldDDIndent = state.ddIndent oldIndent = state.blkIndent oldTShift = state.tShift[ddLine] oldSCount = state.sCount[ddLine] oldParentType = state.parentType state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2 state.tShift[ddLine] = contentStart - state.bMarks[ddLine] state.sCount[ddLine] = offset state.tight = True state.parentType = "deflist" state.md.block.tokenize(state, ddLine, endLine, True) # If any of list item is tight, mark list as tight if not state.tight or prevEmptyEnd: tight = False # Item become loose if finish with empty line, # but we should filter last element, because it means list finish prevEmptyEnd = (state.line - ddLine) > 1 and state.isEmpty(state.line - 1) state.tShift[ddLine] = oldTShift state.sCount[ddLine] = oldSCount state.tight = oldTight state.parentType = oldParentType state.blkIndent = oldIndent state.ddIndent = oldDDIndent token = state.push("dd_close", "dd", -1) itemLines[1] = nextLine = state.line if nextLine >= endLine: break_outer = True break if state.sCount[nextLine] < state.blkIndent: break_outer = True break contentStart = skipMarker(state, nextLine) if contentStart < 0: break ddLine = nextLine # go to the next loop iteration: # insert DD tag and repeat checking if break_outer: break_outer = False break if nextLine >= endLine: break dtLine = nextLine if state.isEmpty(dtLine): break if state.sCount[dtLine] < state.blkIndent: break ddLine = dtLine + 1 if ddLine >= endLine: break if state.isEmpty(ddLine): ddLine += 1 if ddLine >= endLine: break if state.sCount[ddLine] < state.blkIndent: break contentStart = skipMarker(state, ddLine) if contentStart < 0: break # go to the next loop iteration: # insert DT and DD tags and repeat checking # Finalise list token = state.push("dl_close", "dl", -1) listLines[1] = nextLine state.line = nextLine # mark paragraphs tight if needed if tight: markTightParagraphs(state, listTokIdx) return True
def _fieldlist_rule(state: StateBlock, startLine: int, endLine: int, silent: bool): # adapted from markdown_it/rules_block/list.py::list_block # if it's indented more than 3 spaces, it should be a code block if state.sCount[startLine] - state.blkIndent >= 4: return False posAfterName, name_text = parseNameMarker(state, startLine) if posAfterName < 0: return False # For validation mode we can terminate immediately if silent: return True # start field list token = state.push("field_list_open", "dl", 1) token.attrSet("class", "field-list") token.map = listLines = [startLine, 0] # iterate list items nextLine = startLine with set_parent_type(state, "fieldlist"): while nextLine < endLine: # create name tokens token = state.push("fieldlist_name_open", "dt", 1) token.map = [startLine, startLine] token = state.push("inline", "", 0) token.map = [startLine, startLine] token.content = name_text token.children = [] token = state.push("fieldlist_name_close", "dt", -1) # set indent positions pos = posAfterName maximum = state.eMarks[nextLine] offset = ( state.sCount[nextLine] + posAfterName - (state.bMarks[startLine] + state.tShift[startLine]) ) # find indent to start of body on first line while pos < maximum: ch = state.srcCharCode[pos] if ch == 0x09: # \t offset += 4 - (offset + state.bsCount[nextLine]) % 4 elif ch == 0x20: # \s offset += 1 else: break pos += 1 contentStart = pos # set indent for body text if contentStart >= maximum: # no body on first line, so use constant indentation # TODO adapt to indentation of subsequent lines? indent = 2 else: indent = offset # Run subparser on the field body token = state.push("fieldlist_body_open", "dd", 1) token.map = itemLines = [startLine, 0] # change current state, then restore it after parser subcall oldTShift = state.tShift[startLine] oldSCount = state.sCount[startLine] oldBlkIndent = state.blkIndent state.tShift[startLine] = contentStart - state.bMarks[startLine] state.sCount[startLine] = offset state.blkIndent = indent state.md.block.tokenize(state, startLine, endLine) state.blkIndent = oldBlkIndent state.tShift[startLine] = oldTShift state.sCount[startLine] = oldSCount token = state.push("fieldlist_body_close", "dd", -1) nextLine = startLine = state.line itemLines[1] = nextLine if nextLine >= endLine: break contentStart = state.bMarks[startLine] # Try to check if list is terminated or continued. if state.sCount[nextLine] < state.blkIndent: break # if it's indented more than 3 spaces, it should be a code block if state.sCount[startLine] - state.blkIndent >= 4: break # get next field item posAfterName, name_text = parseNameMarker(state, startLine) if posAfterName < 0: break # Finalize list token = state.push("field_list_close", "dl", -1) listLines[1] = nextLine state.line = nextLine return True