X-Git-Url: http://git.plutz.net/?a=blobdiff_plain;f=markdown.awk;h=7e29c572213e553df07563099cf52683ed11b960;hb=426dac52b5648fea4854fb141452d113b5c2251a;hp=b0647922efdcbf5859dac4d3cda72145035403ed;hpb=49a67fed963936a4e88b2a8904296a16a4bbb9eb;p=cgilite
diff --git a/markdown.awk b/markdown.awk
index b064792..7e29c57 100755
--- a/markdown.awk
+++ b/markdown.awk
@@ -4,10 +4,20 @@
# EXPERIMENTAL Markdown processor with minimal dependencies.
# Meant to support all features of John Grubers basic Markdown
# + a number of common extensions, mostly inspired by Pandoc Markdown
-#
-# ToDo:
-# - HTML processing / escaping (according to environment flag)
-# - em-dashes and arrows
+
+# Copyright 2021 - 2023 Paul Hänsch
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED âAS ISâ AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# Supported Features / TODO:
# ==========================
@@ -42,18 +52,24 @@
#
# Extensions - Block elements:
# ----------------------------
+# - [x] Automatic -wrapping (custom)
# - ? Heading identifiers (php md, pandoc)
+# - [x] Heading attributes (custom)
+# - [ ]
terminates section
# - [x] Automatic heading identifiers (custom)
# - [x] Fenced code blocks (php md, pandoc)
# - [x] Fenced code attributes
-# - [ ] Tables
+# - [x] Images (as block elements, ":""); }
+ HL[hlvl]++; for ( n = hlvl + 1; n <= 6; n++) { HL[n] = 0;}
+
+ hid = ""; for ( n = 2; n <= blvl; n++) { hid = hid BL[n] "/"; }
+ hid = hid HL[1]; for ( n = 2; n <= hlvl; n++) { hid = hid "." HL[n] ; }
+ hid = hid ":" URL(htxt, 1);
+
+ sub(/([0-9]+( [0-9]+){5})$/, "", hstack);
+ hstack = hstack HL[1] " " HL[2] " " HL[3] " " HL[4] " " HL[5] " " HL[6];
+
+ return sec "" \
+ "" inline( htxt ) \
+ "" \
+ "\n";
+}
+
+# Nested Block, resets heading counters
+function _nblock( block, LOCAL, sec, n ) {
+ hstack = hstack " 0 0 0 0 0 0";
+
+ # Block Level
+ blvl++; BL[blvl]++;
+ for ( n = blvl + 1; n in BL; n++) { delete BL[n]; }
+
+ block = _block( block );
+ match(hstack, /([0-9]+( [0-9]+){5})$/); split( substr(hstack, RSTART), HL);
+ sec = ""; for ( n = 1; n <= 6; n++ ) { sec = sec (HL[n]?"":""); }
+
+ sub("( +[0-9]+){6} *$", "", hstack); blvl--;
+ return block sec;
+}
+
+function _block( block, LOCAL, st, len, text, title, attrib, href, guard, code, indent, list ) {
+ gsub( "(^\n+|\n+$)", "", block );
if ( block == "" ) {
return "";
@@ -291,21 +399,150 @@ function _block( block, LOCAL, st, len, hlvl, htxt, guard, code, indent, attrib
} else if ( match( block, /^> /) ) {
match( block, /(^|\n)[[:space:]]*(\n|$)/ ) || match(block, /$/);
len = RLENGTH; st = RSTART;
- return "
\n" _block( gensub( /(^|\n)> /, "\n", "g", substr(block, 1, st - 1) ) ) "
\n\n" \
- _block( substr(block, st + len) );
+ text = substr(block, 1, st - 1); gsub( /(^|\n)> /, "\n", text );
+ text = _nblock( text ); gsub( /^\n|\n$/, "", text )
+ return "" text "
\n\n" _block( substr(block, st + len) );
+
+ # Pipe Tables (pandoc / php md / gfm )
+ } else if ( match(block, "^((\\|)?([^\n]+\\|)+[^\n]+(\\|)?)\n" \
+ "((\\|)?(:?-+:?[\\|+])+:?-+:?(\\|)?)\n" \
+ "((\\|)?([^\n]+\\|)+[^\n]+(\\|)?(\n|$))+" ) ) {
+ len = RLENGTH; st = RSTART;
+ #initialize empty arrays
+ split("", talign); split("", tarray);
+ cols = 0; cnt=0; ttext = "";
+
+ # table header and alignment
+ split( gensub( /(^\||\|$)/, "", "g", \
+ gensub( /(^|[^\\])\\\|/, "\\1\\|", "g", \
+ substr(block, 1, match(block, /(\n|$)/)) \
+ )), tarray, /\|/);
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+ cols = split( \
+ gensub( /(^\||\|$)/, "", "g", \
+ substr(block, 1, match(block, /(\n|$)/)) \
+ ), talign, /[+\|]/);
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+
+ for( cnt = 1; cnt < cols; cnt++ ) {
+ if (match(talign[cnt], /:-+:/)) talign[cnt]="center";
+ else if (match(talign[cnt], /-+:/)) talign[cnt]="right";
+ else if (match(talign[cnt], /:-+/)) talign[cnt]="left";
+ else talign[cnt]="";
+ }
+
+ ttext = "\n"
+ for (cnt = 1; cnt < cols; cnt++)
+ ttext = ttext "" inline(tarray[cnt]) " | "
+ ttext = ttext "
\n\n"
+
+ while ( match(block, "^((\\|)?([^\n]+\\|)+[^\n]+(\\|)?(\n|$))+" ) ){
+ split( gensub( /(^\||\|$)/, "", "g", \
+ gensub( /(^|[^\\])\\\|/, "\\1\\|", "g", \
+ substr(block, 1, match(block, /(\n|$)/)) \
+ )), tarray, /\|/);
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+
+ ttext = ttext ""
+ for (cnt = 1; cnt < cols; cnt++)
+ ttext = ttext "" inline(tarray[cnt]) " | "
+ ttext = ttext "
\n"
+ }
+ return "\n" _block(block);
+
+ # Grid Tables (pandoc)
+ # (with, and without header)
+ } else if ( match( block, "^\\+(-+\\+)+\n" \
+ "(\\|([^\n]+\\|)+\n)+" \
+ "(\\+(:?=+:?\\+)+)\n" \
+ "((\\|([^\n]+\\|)+\n)+" \
+ "\\+(-+\\+)+(\n|$))+" \
+ ) || \
+ match( block, "^()()()" \
+ "(\\+(:?-+:?\\+)+)\n" \
+ "((\\|([^\n]+\\|)+\n)+" \
+ "\\+(-+\\+)+(\n|$))+" \
+ ) ) {
+ len = RLENGTH; st = RSTART;
+ #initialize empty arrays
+ split("", talign); split("", tarray); split("", tread);
+ cols = 0; cnt=0; ttext = "";
+
+ # Column Count
+ cols = split( gensub( "^(\\+(:?-+:?\\+)+)(\n.*)*$", "\\1", 1, block), tread, /\+/) - 2;
+ # debug(" Cols: " gensub( "^(\\+(:?-+:?\\+)+)(\n.*)*$", "\\1", 1, block ));
+
+ # table alignment
+ split( gensub( "^(.*\n)?\\+((:?=+:?\\+|(:-+|-+:|:-+:)\\+)+)(\n.*)$", "\\2", "g", block ), talign, /\+/ );
+ # debug("Align: " gensub( "^(.*\n)?\\+((:?=+:?\\+|(:-+|-+:|:-+:)\\+)+)(\n.*)$", "\\2", "g", block ));
+
+ for (cnt = 1; cnt <= cols; cnt++) {
+ if (match(talign[cnt], /:(-+|=+):/)) talign[cnt]="center";
+ else if (match(talign[cnt], /(-+|=+):/)) talign[cnt]="right";
+ else if (match(talign[cnt], /:(-+|=+)/ )) talign[cnt]="left";
+ else talign[cnt]="";
+ }
+
+ if ( match(block, "^\\+(-+\\+)+\n" \
+ "(\\|([^\n]+\\|)+\n)+" \
+ "\\+(:?=+:?\\+)+\n" \
+ "((\\|([^\n]+\\|)+\n)+" \
+ "\\+(-+\\+)+(\n|$))+" \
+ ) ) {
+ # table header
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+ while ( match(block, "^\\|([^\n]+\\|)+\n") ) {
+ split( gensub( /(^\||\|$)/, "", "g", \
+ gensub( /(^|[^\\])\\\|/, "\\1\\|", "g", \
+ substr(block, 1, match(block, /(\n|$)/)) \
+ )), tread, /\|/);
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+ for (cnt = 1; cnt <= cols; cnt++)
+ tarray[cnt] = tarray[cnt] "\n" tread[cnt];
+ }
+
+ ttext = "\n"
+ for (cnt = 1; cnt <= cols; cnt++)
+ ttext = ttext "" _nblock(tarray[cnt]) " | "
+ ttext = ttext "
\n"
+ }
+
+ # table body
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+ ttext = ttext "\n"
+
+ while ( match(block, /^((\|([^\n]+\|)+\n)+\+(-+\+)+(\n|$))+/ ) ){
+ split("", tarray);
+ while ( match(block, /^\|([^\n]+\|)+\n/) ) {
+ split( gensub( /(^\||\|$)/, "", "g", \
+ gensub( /(^|[^\\])\\\|/, "\\1\\|", "g", \
+ substr(block, 1, match(block, /(\n|$)/)) \
+ )), tread, /\|/);
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+ for (cnt = 1; cnt <= cols; cnt++)
+ tarray[cnt] = tarray[cnt] "\n" tread[cnt];
+ }
+ block = substr(block, match(block, /(\n|$)/) + 1 );
+
+ ttext = ttext ""
+ for (cnt = 1; cnt <= cols; cnt++)
+ ttext = ttext "" _nblock(tarray[cnt]) " | "
+ ttext = ttext "
\n"
+ }
+ return "\n" _nblock(block);
# Line Blocks (pandoc)
} else if ( match(block, /^\| [^\n]*(\n|$)(\| [^\n]*(\n|$)|[ \t]+[^\n[:space:]][^\n]*(\n|$))*/) ) {
len = RLENGTH; st = RSTART;
- code = substr(block, 1, len);
- gsub(/\n[[:space:]]+/, " ", code);
- gsub(/\n\| /, "\n", code);
- gsub(/^\| |\n$/, "", code);
- return "" gensub(/\n/, "
\n", "g", inline( code )) "
\n" \
- _block( substr( block, len + 1) );
+
+ text = substr(block, 1, len); gsub(/\n[[:space:]]+/, " ", text);
+ gsub(/\n\| /, "\n", text); gsub(/^\| |\n$/, "", text);
+ text = inline(text); gsub(/\n/, "
\n", text);
+
+ return "" text "
\n" _block( substr( block, len + 1) );
# Indented Code Block
- } else if ( match(block, /^( |\t)[^\n]+(\n|$)(( |\t)[^\n]+(\n|$)|[ \t]*(\n|$))*/) ) {
+ } else if ( match(block, /^( |\t)( *\t*[^ \t\n]+ *\t*)+(\n|$)(( |\t)[^\n]+(\n|$)|[ \t]*(\n|$))*/) ) {
len = RLENGTH; st = RSTART;
code = substr(block, 1, len);
gsub(/(^|\n)( |\t)/, "\n", code);
@@ -316,13 +553,13 @@ function _block( block, LOCAL, st, len, hlvl, htxt, guard, code, indent, attrib
# Fenced Divs (pandoc, custom)
} else if ( match( block, /^(:::+)/ ) ) {
guard = substr( block, 1, RLENGTH );
- code = gensub(/^[^\n]+\n/, "", 1, block);
+ code = block; sub(/^[^\n]+\n/, "", code);
attrib = gensub(/^:::+[ \t]*\{?[ \t]*([^\}\n]*)\}?[ \t]*\n.*$/, "\\1", 1, block);
gsub(/[^a-zA-Z0-9_-]+/, " ", attrib);
gsub(/(^ | $)/, "", attrib);
if ( match(code, "(^|\n)" guard "+(\n|$)" ) ) {
len = RLENGTH; st = RSTART;
- return "" _block( substr(code, 1, st - 1) ) "
\n" \
+ return "" _nblock( substr(code, 1, st - 1) ) "
\n" \
_block( substr( code, st + len ) );
} else {
match( block, /(^|\n)[[:space:]]*(\n|$)/ ) || match( block, /$/ );
@@ -335,7 +572,7 @@ function _block( block, LOCAL, st, len, hlvl, htxt, guard, code, indent, attrib
} else if ( match( block, /^(~~~+|```+)/ ) ) {
guard = substr( block, 1, RLENGTH );
code = gensub(/^[^\n]+\n/, "", 1, block);
- attrib = gensub(/^:::+[ \t]*\{?[ \t]*([^\}\n]*)\}?[ \t]*\n.*$/, "\\1", 1, block);
+ attrib = gensub(/^(~~~+|```+)[ \t]*\{?[ \t]*([^\}\n]*)\}?[ \t]*\n.*$/, "\\2", 1, block);
gsub(/[^a-zA-Z0-9_-]+/, " ", attrib);
gsub(/(^ | $)/, "", attrib);
if ( match(code, "(^|\n)" guard "+(\n|$)" ) ) {
@@ -349,53 +586,147 @@ function _block( block, LOCAL, st, len, hlvl, htxt, guard, code, indent, attrib
_block( substr(block, st + len) );
}
- # Unordered list
- } else if ( match( block, "^ ? ? ?[-+*][ \t]+[^\n]+(\n|$)" \
- "(([ \t]*\n)* ? ? ?[-+*][ \t]+[^\n]+(\n|$)" \
- "|([ \t]*\n)*( ? ? ?\t| +)[^\n]+(\n|$)" \
- "|[^\n]+(\n|$))*" ) ) {
- list = substr( block, 1, RLENGTH);
- block = substr( block, RLENGTH + 1);
- indent = length( gensub(/[-+*][ \t]+[^\n]+.*$/, "", 1, list) );
-
- gsub("(^|\n) {0," indent "}", "\n", list);
- return "\n\n" _list( substr(list, 2) ) "
\n" _block( block );
-
- # Ordered list
- } else if ( match( block, "^ ? ? ?([0-9]+|#)\\.[ \t]+[^\n]+(\n|$)" \
- "(([ \t]*\n)* ? ? ?([0-9]+|#)\\.[ \t]+[^\n]+(\n|$)" \
- "|([ \t]*\n)*( ? ? ?\t| +)[^\n]+(\n|$)" \
- "|[^\n]+(\n|$))*" ) ) {
- list = substr( block, 1, RLENGTH);
- block = substr( block, RLENGTH + 1);
- indent = length( gensub(/([0-9]+|#)\.[ \t]+[^\n]+.*$/, "", 1, list) );
-
- gsub("(^|\n) {0," indent "}", "\n", list);
- return "\n\n" _list( substr(list, 2) ) "
\n" _block( block );
-
- # First Order Heading
- } else if ( match( block, /^[^\n]+\n===+(\n|$)/ ) ) {
- len = RLENGTH;
- HL[1]++; HL[2] = 0; HL[3] = 0; HL[4] = 0; HL[5] = 0; HL[6] = 0;
- return "" inline( gensub( /\n.*$/, "", "g", block ) ) "
\n\n" \
- _block( substr( block, len + 1 ) );
+ # First Order Heading H1 + Attrib
+ } else if ( match( block, /^([^\n]+)([ \t]*\{([^\}\n]+)\})\n===+(\n|$)/ ) ) {
+ len = RLENGTH; text = attrib = block;
+ sub(/([ \t]*\{([^\}\n]+)\})\n===+(\n.*)?$/, "", text);
+ sub(/\}\n===+(\n.*)?$/, "", attrib); sub(/^([^\n]+)[ \t]*\{/, "", attrib);
+ gsub(/[^a-zA-Z0-9_-]+/, " ", attrib); gsub(/(^ | $)/, "", attrib);
- # Second Order Heading
- } else if ( match( block, /^[^\n]+\n---+(\n|$)/ ) ) {
- len = RLENGTH;
- HL[2]++; HL[3] = 0; HL[4] = 0; HL[5] = 0; HL[6] = 0;
- return "" inline( gensub( /\n.*$/, "", "g", block ) ) "
\n\n" \
+ return headline(1, text, attrib) _block( substr( block, len + 1 ) );
+
+ # First Order Heading H1
+ } else if ( match( block, /^([^\n]+)\n===+(\n|$)/ ) ) {
+ len = RLENGTH; text = substr(block, 1, len);
+ sub(/\n===+(\n.*)?$/, "", text);
+
+ return headline(1, text, 0) _block( substr( block, len + 1 ) );
+
+ # Second Order Heading H2 + Attrib
+ } else if ( match( block, /^([^\n]+)([ \t]*\{([^\}\n]+)\})\n---+(\n|$)/ ) ) {
+ len = RLENGTH; text = attrib = block;
+ sub(/([ \t]*\{([^\}\n]+)\})\n---+(\n.*)?$/, "", text);
+ sub(/\}\n---+(\n.*)?$/, "", attrib); sub(/^([^\n]+)[ \t]*\{/, "", attrib);
+ gsub(/[^a-zA-Z0-9_-]+/, " ", attrib); gsub(/(^ | $)/, "", attrib);
+
+ return headline(2, text, attrib) _block( substr( block, len + 1) );
+
+ # Second Order Heading H2
+ } else if ( match( block, /^([^\n]+)\n---+(\n|$)/ ) ) {
+ len = RLENGTH; text = substr(block, 1, len);
+ sub(/\n---+(\n.*)?$/, "", text);
+
+ return headline(2, text, 0) _block( substr( block, len + 1) );
+
+ # Nth Order Heading H1 H2 H3 H4 H5 H6 + Attrib
+ } else if ( match( block, /^(#{1,6})[ \t]*(([^ \t\n]+|[ \t]+[^ \t\n#]|[ \t]+#+[ \t]*[^ \t\n#])+)[ \t]*#*([ \t]*\{([a-zA-Z \t-]*)\})(\n|$)/ ) ) {
+ len = RLENGTH; text = attrib = substr(block, 1, len);
+ match(block, /^#{1,6}/); n = RLENGTH;
+
+ sub(/^(#{1,6})[ \t]*/, "", text); sub(/[ \t]*#*([ \t]*\{([a-zA-Z \t-]*)\})(\n.*)?$/, "", text);
+ sub(/^(#{1,6})[ \t]*(([^ \t\n]+|[ \t]+[^ \t\n#]|[ \t]+#+[ \t]*[^ \t\n#])+)[ \t]*#*[ \t]*\{/, "", attrib);
+ sub(/\})(\n.*)?$/, "", attrib);
+ gsub(/[^a-zA-Z0-9_-]+/, " ", attrib); gsub(/(^ | $)/, "", attrib);
+
+ return headline( n, text, attrib ) _block( substr( block, len + 1) );
+
+ # Nth Order Heading H1 H2 H3 H4 H5 H6
+ } else if ( match( block, /^(#{1,6})[ \t]*(([^ \t\n]+|[ \t]+[^ \t\n#]|[ \t]+#+[ \t]*[^ \t\n#])+)[ \t]*#*(\n|$)/ ) ) {
+ len = RLENGTH; text = substr(block, 1, len);
+ match(block, /^#{1,6}/); n = RLENGTH;
+ sub(/^(#{1,6})[ \t]*/, "", text); sub(/[ \t]*#*(\n.*)?$/, "", text);
+
+ return headline( n, text, 0 ) _block( substr( block, len + 1) );
+
+ # block images (wrapped in