# Status: # [.] Inline HTML # [x] Autoescape # [ ] Block elements # [x] Paragraph # [x] Line break # [x] Headers (atx and setex) # [x] Blockquotes # [x] Lists # [ ] Code blocks # [x] Horizontal rules # [ ] Span elements # [.] Links # [x] Inline # [ ] Reference # [x] Emphasis # [x] Code # [.] Images # [x] Inline # [ ] Reference # [ ] Other # [ ] Autolinks # [ ] Escapes # # [ ] Non-standard features # [x] Strikethrough ~~...~~ # [x] Superscript ^...^ # [x] Subscript ~...~ # [ ] Footnote [^...] (with user customizations) # [x] Task list - [ ] (readonly) # [.] Definition lists # [x] Single line # [ ] Multi-line # [ ] Fenced code blocks ```lang ``` # [ ] Tables # No character classes, since they aren't supported on every system BEGIN { prev_blankline = blankline = 1 skipprint = 1 newStack("bq-level") newStack("list-level") } function println(line) { if (0 == line) line = $0 print prev prev = line } function autoinsert(arrname, isbefore) { if (isbefore == "") isbefore = 0 for (confstr in arrname) { split(confstr, conf, SUBSEP) ## FILENAME regex, ".*" (default) if (2 in conf && FILENAME !~ conf[2]) { continue } ## Text regex, ".*" (default) if (3 in conf && text !~ conf[3]) { continue } if (isbefore) prev = arrname[confstr] prev else prev = prev arrname[confstr] ## When, FIRST | ALL if (conf[1] == FIRST) { delete arrname[confstr] } } } { prev_blankline = blankline blankline = 1 } /[^ \t]/ { blankline = 0 } # Inline HTML /^[ \t]*<\/?[0-9A-Za-z]+/ { println() next } # Link definition /^\[.+\]:/ { blankline = 1 next } # Auto-escape & /&/ { ampstart = 0 ## gsub(/&(?![[:alnum:]#]+;)/, "&") while (match(substr($0, ampstart + 1), /&/)) { ampstart += RSTART if (match(substr($0, ampstart), /&[0-9A-Za-z#]+;/)) { continue } $0 = substr($0, 1, ampstart - 1) "&" substr($0, ampstart + 1) } } # Auto-escape < / { gsub(/, "\\<") } # Blockquote Part 1/2 /^>[ \t]*$/ { next } /^> / { level = 0 while (match($0, /^> [ \t]*/)) { $0 = substr($0, RLENGTH + 1) level += RSTART while (topStack("bq-level") > level) { println("") autoinsert(user_BLOCKQUOTE_AFTER) popStack("bq-level") } if (topStack("bq-level") < level) { autoinsert(user_BLOCKQUOTE_BEFORE, 1) println("
") pushStack("bq-level", level) } } } # atx-style headers /^[ \t]*#+/ { match($0, /#+/) if (RLENGTH <= 6) { ## Remove prefix/suffix gsub(/^[ \t]*#+[ \t]*/, "") gsub(/[ \t]*#+[ \t]*$/, "") if (RLENGTH == 1) autoinsert(user_H1_BEFORE, 1) else if (RLENGTH == 2) autoinsert(user_H2_BEFORE, 1) else if (RLENGTH == 3) autoinsert(user_H3_BEFORE, 1) else if (RLENGTH == 4) autoinsert(user_H4_BEFORE, 1) else if (RLENGTH == 5) autoinsert(user_H5_BEFORE, 1) else if (RLENGTH == 6) autoinsert(user_H6_BEFORE, 1) autoinsert(user_H_BEFORE, 1) println("") autoinsert(user_BLOCKQUOTE_AFTER) popStack("bq-level") } } # Links /!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/ { isimg = 0 while (match($0, /!?\[[^\]]*\] ?[(\[]/)) { if (substr($0, RSTART, 1) == "!") { isimg = 1 RSTART++ RLENGTH-- } astart = RSTART ## astart = indexof("[") has_space = substr($0, RSTART + RLENGTH - 2, 1) == " " inner_anchor = substr($0, RSTART + 1, RLENGTH - 3 - has_space) ## ...[xxxxx](...)... aend = RSTART + RLENGTH - 1 match(substr($0, astart), /\] ?[(\[][^)\]]*[\])]/) aend += RLENGTH - 2 - has_space ## aend = indexof(")") href = substr($0, astart + RSTART + 1 + has_space, RLENGTH - 3 - has_space) ## ...[...](xxxxx)... title = "\"\"" if (substr($0, astart + RSTART + has_space, 1) == "[") { ## is reference link if (href == "") href = inner_anchor title = reference[href, "title"] href = reference[href, "href"] } else if (match(href, /[ \t]"[^"]*"[ \t]*$/)) { ## has title title = substr(href, RSTART + 1, RLENGTH - 1) href = substr(href, 1, RSTART - 1) } sub(/[ \t]+/, "", href) if (isimg) elem = "" $0 " ") autoinsert(user_H_AFTER) if (RLENGTH == 1) autoinsert(user_H1_AFTER) else if (RLENGTH == 2) autoinsert(user_H2_AFTER) else if (RLENGTH == 3) autoinsert(user_H3_AFTER) else if (RLENGTH == 4) autoinsert(user_H4_AFTER) else if (RLENGTH == 5) autoinsert(user_H5_AFTER) else if (RLENGTH == 6) autoinsert(user_H6_AFTER) blankline = 1 next } } # setex-style headers !prev_blankline && /^[ \t]*(=|-)+[ \t]*$/ { sub(/^/, "", prev) ## from paragraph logic hlevel = 1 + ($0 ~ /-/) prev = "
" prev " " if (hlevel == 1) autoinsert(user_H1_BEFORE, 1) else autoinsert(user_H2_BEFORE, 1) autoinsert(user_H_BEFORE, 1) autoinsert(user_H_AFTER) if (hlevel == 1) autoinsert(user_H1_AFTER) else autoinsert(user_H2_AFTER) blankline = 1 next } # Horizontal rules /^(-|\*|_) ?(-|\*|_) ?(-|\*|_|[ \t])+$/ { autoinsert(user_HR_BEFORE, 1) println("
") autoinsert(user_HR_AFTER) blankline = 1 next } # Line break / $/ { sub(/[ \t]* $/, "") } # Code blocks /^( |\t)[ \t]*/ { } # Check boxes /^[ \t]*- \[[ x]\][ \t]/ { match($0, /- \[[ x]\][ \t]/) if (substr($0, RSTART + 3, 1) == "x") checked = " checked=\"checked\"" else checked = "" $0 = "" substr($0, RSTART + RLENGTH) "" } # Unordered lists function abs(x) { return ((x < 0) ? -x : x) } !blankline && lengthStack("list-level") > 0 { match($0, /^ */) indent = RLENGTH match($0, /(\*|\+|-|[0-9]+\.) /) if (RLENGTH == -1) RLENGTH = 0 ## indent + RLENGTH = length(" * " or " 1. ") while (indent + RLENGTH < abs(topStack("list-level"))) { if (topStack("list-level") < 0) { println("") autoinsert(user_L_AFTER) autoinsert(user_OL_AFTER) } else { println("") autoinsert(user_L_AFTER) autoinsert(user_UL_AFTER) } popStack("list-level") } } /^[ \t]*(\*|\+|-|[0-9]+\.) / { match($0, /(\*|\+|-|[0-9]+\.) /) if (abs(topStack("list-level")) < RSTART) { if (RLENGTH > 2) { xl = ""; mult = -1 autoinsert(user_OL_BEFORE, 1) } else { xl = "
"; mult = 1 autoinsert(user_UL_BEFORE, 1) } autoinsert(user_L_BEFORE, 1) println(xl "
- ") pushStack("list-level", mult * (RSTART + RLENGTH - 1)) } else { println("
- ") } $0 = substr($0, RSTART + RLENGTH) } # Paragraphs prev_blankline && !blankline && lengthStack("bq-level") == 0 && lengthStack("list-level") == 0 { $0 = "
" $0 } !prev_blankline && blankline && lengthStack("bq-level") == 0 && lengthStack("list-level") == 0 { prev = prev "
" } # Blockquote Part 2/2 blankline { while (lengthStack("bq-level") > 0) { println("
" , "")
surround("`" , "" , "")
}
!prev_blankline && /^: / {
sub(/^/, "", prev) sub(/^: /, "") prev = "