From 6ceb068e6f6ae3302c6033542cfe863f52d0f0e3 Mon Sep 17 00:00:00 2001 From: James Ball Date: Thu, 27 Nov 2025 12:03:18 -0800 Subject: [PATCH] Convert adoc tables to html tables Signed-off-by: James Ball --- converters/tags.rb | 4 +- tests/norm-rule/expected/test-norm-rules.adoc | 34 +++--- tests/norm-rule/expected/test-norm-rules.html | 6 +- tests/norm-rule/expected/test-norm-rules.json | 6 +- tests/norm-rule/expected/test-norm-rules.xlsx | Bin 8794 -> 8799 bytes tests/norm-rule/expected/test-norm-tags.json | 6 +- tests/norm-rule/test.adoc | 5 +- tools/create_normative_rules.rb | 101 +++++++++++++++++- 8 files changed, 127 insertions(+), 35 deletions(-) diff --git a/converters/tags.rb b/converters/tags.rb index 967b396..05b0a32 100644 --- a/converters/tags.rb +++ b/converters/tags.rb @@ -144,8 +144,8 @@ def node_text_content(node) cell.content.join("\n") end end - # Separate cells by |. - end.join("|") + # Prefix cells with "|" + end.map { |s| '|' + s }.join # Separate rows by newlines. end.join("\n") # Separate table sections by ===. diff --git a/tests/norm-rule/expected/test-norm-rules.adoc b/tests/norm-rule/expected/test-norm-rules.adoc index 5d1fca4..8178c00 100644 --- a/tests/norm-rule/expected/test-norm-rules.adoc +++ b/tests/norm-rule/expected/test-norm-rules.adoc @@ -89,15 +89,15 @@ It's got 2 lines. .1+| table1 | === -WITH anchor -WITHOUT anchor +|WITH anchor +|WITHOUT anchor === a| link:test.html#norm:table:anchors-in-cells:entire-table[norm:table:anchors-in-cells:entire-table] .1+| table2 -| Header 1|Header 2 +| |Header 1|Header 2 === -Cell in column 1, row 1|Cell in column 2, row 1 -Cell in column 1, row 2|Cell in column 2, row 2 +|Cell in column 1, row 1|Cell in column 2, row 1 +|Cell in column 1, row 2|Cell in column 2, row 2 === a| link:test.html#norm:table:no-anchors-in-cells:entire-table[norm:table:no-anchors-in-cells:entire-table] .1+| table3 @@ -107,19 +107,19 @@ Cell in column 1, row 2|Cell in column 2, row 2 | ABC DEF a| link:test.html#norm:table:anchors-in-cells:entire-table-not-tagged:cell[norm:table:anchors-in-cells:entire-table-not-tagged:cell] .1+| table5 -| Name|Color +| |Name|Color === -Roses|Red -Violets|Blue -Name1|Color1 -Name2|Color2 -Name3|Color3 -Name4|Color4 -Name5|Color5 -Name6|Color6 -Name7|Color8 -Name9|Color9 -Name10|Color10 +|Roses|Red +|Violets|Blue +|Name1|Color1 +|Name2|Color2 +|Name3|Color3 +|Name4|Color4 +|Name5|Color5 +|Name6|Color6 +|Name7|Color7 +|Name8|Color8 +|Name9|Color9 ... a| link:test.html#norm:table:many-rows[norm:table:many-rows] .1+| unordered1 diff --git a/tests/norm-rule/expected/test-norm-rules.html b/tests/norm-rule/expected/test-norm-rules.html index c196db4..6aba58c 100644 --- a/tests/norm-rule/expected/test-norm-rules.html +++ b/tests/norm-rule/expected/test-norm-rules.html @@ -255,12 +255,12 @@

my-chapter_name

table1 - ===
WITH anchor
WITHOUT anchor
=== +
WITH anchor
WITHOUT anchor
norm:table:anchors-in-cells:entire-table table2 - Header 1|Header 2
===
Cell in column 1, row 1|Cell in column 2, row 1
Cell in column 1, row 2|Cell in column 2, row 2
=== +
Header 1Header 2
Cell in column 1, row 1Cell in column 2, row 1
Cell in column 1, row 2Cell in column 2, row 2
norm:table:no-anchors-in-cells:entire-table @@ -275,7 +275,7 @@

my-chapter_name

table5 - Name|Color
===
Roses|Red
Violets|Blue
Name1|Color1
Name2|Color2
Name3|Color3
Name4|Color4
Name5|Color5
Name6|Color6
Name7|Color8
Name9|Color9
Name10|Color10
... +
NameColor
RosesRed
VioletsBlue
Name1Color1
Name2Color2
Name3Color3
Name4Color4
Name5Color5
Name6Color6
Name7Color7
Name8Color8
Name9Color9
Name10Color10
......
norm:table:many-rows diff --git a/tests/norm-rule/expected/test-norm-rules.json b/tests/norm-rule/expected/test-norm-rules.json index 8f51d59..b4294e4 100644 --- a/tests/norm-rule/expected/test-norm-rules.json +++ b/tests/norm-rule/expected/test-norm-rules.json @@ -333,7 +333,7 @@ "tags": [ { "name": "norm:table:anchors-in-cells:entire-table", - "text": "===\nWITH anchor\nWITHOUT anchor\n===", + "text": "===\n|WITH anchor\n|WITHOUT anchor\n===", "tag_filename": "/build/test-norm-tags.json", "stds_doc_url": "test.html" } @@ -346,7 +346,7 @@ "tags": [ { "name": "norm:table:no-anchors-in-cells:entire-table", - "text": "Header 1|Header 2\n===\nCell in column 1, row 1|Cell in column 2, row 1\nCell in column 1, row 2|Cell in column 2, row 2\n===", + "text": "|Header 1|Header 2\n===\n|Cell in column 1, row 1|Cell in column 2, row 1\n|Cell in column 1, row 2|Cell in column 2, row 2\n===", "tag_filename": "/build/test-norm-tags.json", "stds_doc_url": "test.html" } @@ -385,7 +385,7 @@ "tags": [ { "name": "norm:table:many-rows", - "text": "Name|Color\n===\nRoses|Red\nViolets|Blue\nName1|Color1\nName2|Color2\nName3|Color3\nName4|Color4\nName5|Color5\nName6|Color6\nName7|Color8\nName9|Color9\nName10|Color10\nName11|Color11\nName12|Color12\nName13|Color13\nName14|Color14\n===", + "text": "|Name|Color\n===\n|Roses|Red\n|Violets|Blue\n|Name1|Color1\n|Name2|Color2\n|Name3|Color3\n|Name4|Color4\n|Name5|Color5\n|Name6|Color6\n|Name7|Color7\n|Name8|Color8\n|Name9|Color9\n|Name10|Color10\n|Name11|Color11\n|Name12|Color12\n|Name13|Color13\n|Name14|Color14\n===", "tag_filename": "/build/test-norm-tags.json", "stds_doc_url": "test.html" } diff --git a/tests/norm-rule/expected/test-norm-rules.xlsx b/tests/norm-rule/expected/test-norm-rules.xlsx index eb78facabc7eec85f0f5c522c88b24a0507ab566..93a299bc4b6091520f93cfbaefe9de31b3e0a3d3 100644 GIT binary patch delta 2271 zcmZ9OX*d*&8ir>u_HAs17{r){A&w$(EJI<)mTm0Dz73It3?Uh7O&pRK+t}AI)|Sx- z*%fLcyN|486yb|=o$EU1{JO9Eeg3`gkLUfQH=~C$LoqT500011fI&f|{eps00u%@U zm@@zX;6GD$EXM2>HYh|9gT3XU5FX&4_jtgUa*;QF^DcW)JEEWH18!yv=q-=cwM0ko zk3zvs(HZ0F(Fc8L^U@iX>6JN$VPWR28fDO5pyiSgK`Fa5zF`_``5nQCnW|BiCqG}| zUbv)=m=U=(&>tHg+xMl$*?7s5C0@&di!~RUbq{#u-%ftlge^6$44czLNVX44KD+k+ zRQ(K8*olf2WP;kdkXcH@qzb_oe#XDhsu2d<$9QYUa*GpEzBa1HSRQ#&3LMljIr9>7 zKN8ehd7N%gn*$od{%MPezOOZ*h^16KIubHj3!kfI+Md)uq<$v9tLM?0a$ov&_lGkxgoj4(xd*?)PJUgf35Wz?w_sJ>f{`%B<=07($NBAuS z$0^qDjH)D!^`jcy1I83g+8rXQ)3dzoY?6NHRivrb&g>!SJ#-W^zF8)>@EIAx2V!E? zU;^^Gb34;|z)?j>tXI~?KMf3sm@Nfsx$BVVvr8TIHjI*N-wnHk*T<(*2`OCY{?JUos_`tm~N~j?7PU z>E?GQNO+-JP}ie)<1O_^Y$nxYai&_)LUUIc2*S_m^_w zc8&DwofWI4ye-iLy(vh-tyh+8u&Fl?b5KLnAy-MEl7ySfPA6=)9Ke9*5|y9^r(YlN zc%9I+yl?lhS9pBaU5hFl~L+7SAp0>pnPUz43tgT{4&zxGdag% zOc=i34i|4rNo$BvK*jmHibe(=OvS!4+odqAl|RPnmd6x`_hj@*TW>(DGxRy3H}XlE zdT49EEIQ(ID%hsPP@4zW7_1lD=*=l$%C#6Bs=V{0NRl!;*2N{&U<{KIo7*OMY$~rs z|CS{?H`5e{!&>CkImY@!_r5*FzD%5v6WsInB?&)L&WDyH4)tD$@*SMYS1pz0-S=UP zZY5XMbbP;Sl{w*iRS^{ehScHV068S7FKikVs-!-$Y>Xl+d9IoeN9ttkKGTJIc9lJjO1_SG8vt1DRl97nQ61!);C-JeWgc*Pu2U2 zpL^gt4HhQhL7Qllj#Utsx4`zL;R(jU!|7}h%k?Jcb)0XFE}y?r%DMGW^}&U`{@i3A z)rzDRNT8<*yKRaG`yR&^#b)o3D*Hfp2M+f0hO%eECEp&EXdX~*E4z@&%$soH>7&^? z?c8h-;`k`}s#uFj5Q3kUSxKqvQ|C~5-1T*Kw=?NwT+IFf9;4*%-d1s$zPim@G)dNa zRrhF=fRodhktJ!2DlHh-v=+GCGP&oqqqG2a5`fBvv#aO0*rS>+E&BF-7|$wgR%xm( z4=`Ex_PuV|{`Av_@qd_Tj*UMrrK{{ZSPtcRj(tqA29x(&8Ej0K3S0F4J#ELslQf>?g`0@c_kL_e~GhuoVmg-`X zyLzWrM=Re`ugz(N$5!sj+vTy8t*-9|)^9#E(P=sr+;^O`MAZ{zSKUh0yi9>utlPr? zXRU7Paqn&qLbbhC)A3oz@-JTc?0ba`_dUOkFgiuT6!)%JakXd%>}gHCSy zvX1L1kIcThO-E29KjK-jM03N6Nj{H?jUJ0vSpbi|iBW%4ZWF_#P#4cfJHFCYwxO+n zS*{wTYWZ#1!ystq?2PjxnR`NMP@7qb<i^f{CrOo)gjebu Ru!n$eFP{90052x5X2bw=>gaW zI0pb=&j|nsk$s?|gp%x$Ydsp^#0{jy$CyQfXV03lWuHT8_hT8iPJyMw9qqK zpNPm3skQw(!*RwPVTSXM;k%#}$~Z*cHC=e@?a|s+$exSp#F?BQubz3QjoVcSAd6IsHoy^^X;wVn(FfYQLXcqVXpRC!3;}HiZg4oP|MCdnVfhoUP4}$*Y zDAxxNd*8@MV>>xTfW=mVAnSJ=4vFCqW?x=ZJE^~p3jo-Z0suq-P^t1J&lpzg@t%zDuO=*Yncs4S^7qgW#8pi?H!zxOF>^wJ<&}}_Zb%PGLRFg zu&ubrR7q2MVY^fSBOAp&(xi6g-=8s2=bbb@TNpO2wWPO=akyKxL?++`mQZK!d>XfA zlrW`|Ff{1huz~YDO0*uRJ*X-6ke^bi3G=QvgP1#l+wOUc4R%g&78yOW-;?446j z#iH*drjiQ50Uy3vjsum;^roUE@%s@rB74o~c$XJ2>9Y}jlkJ%X$FK9KDK%HFy(kVS zi%~QOCf+hH&DmepAyo%l?trvu^s^4 z_T(m+IW;Cue#-0_`SM+#D21sSo-;_5XKD~BVS-~YGx%VjV@q1GvTjL5<;$AIivCp> z?g&@O)uiC{KTG`g(N&U(JTvDN=Mx<`%uwla6vYc)p%*eO_~15WHMcQt7p~@g!)^2x zJf47+XV3@+)vsK2IFf5fVWvhBw+6?{Lw`hgC)_(Fl8ikQb+EebarqtYAK>?)>V1&W z*J=xYpXkD`T^?H_-6kZ83ME7zxoi_+VZr6{{H=4FRxBdpX+9ncdX3!Syo@J)OsI| z-iRDKC?0A(IV=^;05`tWN(boxejaYAzu0F4Cduj2r({Ib7f^!633vfLBGdWwPm)bX zuzd-^Ru3YGP>`weGM-_+HuwAHp(Y%SEMRTA3sKrDj)5C=wN9R%>U6}ONT8A?0GBd8 zzP$pHtt!opNz%<3T&wL$Kd*OEBM3FD%qt!@g>Z*Nwbb(*7GUjhC3uHC!X}m#l+Z|{ zYgv)1tgnNV1yy{>w8X9_^Xpy%b>8gsA*%POxL)=}h-Az{)IIxk8q|AkVA&c;+aTCR z<@9ER_ktkbou?vANXQ=Eo7Q$?*-%5FHzIs zrFd!sywsn%HEa_T_>iGf+|qJ#3wv(vLA;Q|1f@oaw0ZWnLW&wJZDC-%Y-!!^1#;MS ztv1_I^jNznUT*Y{jrc`pqQ(Tan83I+^KRYmYIXkUy8ExjO66Q8tM@#@n*Oel*ep@q z=B~??rf&sU+RaB}W`k~Ipu#gBy{^|EGxuj=-cdzYwv|8Jz@;|+EtNR8X4<~jw)UC# z&1A1(YpLJ@z=pAy-etFh3I}rqpr_oAjdiRojn|811W^UK8HlpAD8s zZ$x9F7^zkcy?ILKZHc z5Of8P_ZWkZOC>AgVAaMxzJxuutFBoy5-P+P^hp(@qlyJw2laaH`GMxrfHIN~D#qh3 z3(+`5%H-6TA3Ubhck`2;u?65-RwE;nV?@#}6*t8HUNm*SL9ZdOd5v$!IO7TT{rtrI zQvW~vLL{cU9b&&nCO6bwFWm`#Iao5&i;R#_x!|W#J8sfXF3pY1KGaP)re=NO@wQf7 zYR?|uyrK_LpU32(a5SVyFjVXu>glcCm6bd&Yit+#uctUE6T)%ye|>^%1=VMNz62h5$s@Qlq3q;86004u diff --git a/tests/norm-rule/expected/test-norm-tags.json b/tests/norm-rule/expected/test-norm-tags.json index da22278..fb8a56e 100644 --- a/tests/norm-rule/expected/test-norm-tags.json +++ b/tests/norm-rule/expected/test-norm-tags.json @@ -24,11 +24,11 @@ "norm:hyperlink4": "DEF <<non-norm-anchor,custom text>> GHI", "norm:hyperlink5": "GHI <<norm:superscript>> and <<norm:subscript>> JKL", "norm:hyperlink6": "JKL <<norm:superscript,hello>> and <<norm:subscript,goodbye>> MNO", - "norm:table:no-anchors-in-cells:entire-table": "Header 1|Header 2\n===\nCell in column 1, row 1|Cell in column 2, row 1\nCell in column 1, row 2|Cell in column 2, row 2\n===", + "norm:table:no-anchors-in-cells:entire-table": "|Header 1|Header 2\n===\n|Cell in column 1, row 1|Cell in column 2, row 1\n|Cell in column 1, row 2|Cell in column 2, row 2\n===", "norm:table:anchors-in-cells:entire-table-tagged:cell": "WITH anchor", - "norm:table:anchors-in-cells:entire-table": "===\nWITH anchor\nWITHOUT anchor\n===", + "norm:table:anchors-in-cells:entire-table": "===\n|WITH anchor\n|WITHOUT anchor\n===", "norm:table:anchors-in-cells:entire-table-not-tagged:cell": "ABC DEF", - "norm:table:many-rows": "Name|Color\n===\nRoses|Red\nViolets|Blue\nName1|Color1\nName2|Color2\nName3|Color3\nName4|Color4\nName5|Color5\nName6|Color6\nName7|Color8\nName9|Color9\nName10|Color10\nName11|Color11\nName12|Color12\nName13|Color13\nName14|Color14\n===", + "norm:table:many-rows": "|Name|Color\n===\n|Roses|Red\n|Violets|Blue\n|Name1|Color1\n|Name2|Color2\n|Name3|Color3\n|Name4|Color4\n|Name5|Color5\n|Name6|Color6\n|Name7|Color7\n|Name8|Color8\n|Name9|Color9\n|Name10|Color10\n|Name11|Color11\n|Name12|Color12\n|Name13|Color13\n|Name14|Color14\n===", "norm:unordered-list:no-anchors-in-items:entire-list": "Item A\nItem B\nItem C", "norm:unordered-list:anchors-in-items:item1": "Item 1", "norm:unordered-list:anchors-in-items:item2": "Item 2", diff --git a/tests/norm-rule/test.adoc b/tests/norm-rule/test.adoc index 8240eb7..0396bd7 100644 --- a/tests/norm-rule/test.adoc +++ b/tests/norm-rule/test.adoc @@ -118,7 +118,7 @@ Here's some text that isn't normative but the normatively tagged text has an exi [[norm:table:anchors-in-cells:entire-table]] |=== -// FAILS - Want tagged text to be "cell with anchor" +// FAILS - Want tagged text to be "WITH anchor" | [#norm:table:anchors-in-cells:entire-table-tagged:cell]#WITH anchor# | WITHOUT anchor |=== @@ -145,7 +145,8 @@ a| Cell is adoc |Name4|Color4 |Name5|Color5 |Name6|Color6 -|Name7|Color8 +|Name7|Color7 +|Name8|Color8 |Name9|Color9 |Name10|Color10 |Name11|Color11 diff --git a/tools/create_normative_rules.rb b/tools/create_normative_rules.rb index 2e02014..db8376a 100644 --- a/tools/create_normative_rules.rb +++ b/tools/create_normative_rules.rb @@ -15,6 +15,8 @@ NORM_PREFIX = "norm:" +MAX_TABLE_ROWS = 12 # Max rows of a table displayed in a cell. + ################################### # Classes for Normative Rule Tags # ################################### @@ -1016,7 +1018,7 @@ def html_chapter_table(f, table_num, chapter_name, nr_defs, tags, tag_fname2url) unless nr.description.nil? f.puts(%Q{ }) unless row_started - f.puts(%Q{ #{html_convert_newlines(nr.description)}}) + f.puts(%Q{ #{convert_newlines_to_html(nr.description)}}) f.puts(%Q{ Rule's "description" property}) f.puts(%Q{ }) row_started = false @@ -1052,8 +1054,7 @@ def html_chapter_table(f, table_num, chapter_name, nr_defs, tags, tag_fname2url) html_fname = tag_fname2url[tag.tag_filename] fatal("No fname tag to HTML mapping (-tag2url cmd line arg) for tag fname #{tag.tag_filename} for tag name #{tag.name}") if html_fname.nil? - tag_text = html_convert_newlines(limit_table_rows(Adoc2HTML::convert(tag.text))) - + tag_text = convert_newlines_to_html(convert_tags_tables_to_html(Adoc2HTML::convert(tag.text))) # Convert adoc links to normative text in tag text to html links. # @@ -1139,6 +1140,96 @@ def html_script(f) ) end +# Convert the tagged text containing entire tables. Uses format created by "tags" Asciidoctor backend. +# +# Two possible formats: +# +# Without heading: +# +# === +# | ABC | DEF +# |GHI |JKL +# === +# +# Actual string from tags: "===\n| ABC | DEF\n|GHI |JKL\n===" +# +# With heading: +# +# | H1 | H2 +# === +# | GHI | JKL +# === +# +# Actual string from tags: "| H1 | H2\n===\n| GHI | JKL\n===" + +def convert_tags_tables_to_html(text) + raise ArgumentError, "Expected String for text but was passed a #{text}.class" unless text.is_a?(String) + + ret = text # Default to input + + text.gsub(/(.*?)===\n(.+)\n===/m) do + # Found a "tags" formatted table + heading = $1.chomp # Remove trailing newline + rows = $2.split("\n") # Split into array of rows + + ret = "".dup # Start html table + + # Add heading if present + heading_cells = extract_tags_table_cells(heading) + unless heading_cells.empty? + ret << "" + ret << "" + ret << heading_cells.map { |cell| "" }.join("") + ret << "" + ret << "" + end + + # Add each row + ret << "" + rows.each_with_index do |row,index| + if index < MAX_TABLE_ROWS + ret << "" + row_cells = extract_tags_table_cells(row) + ret << row_cells.map { |cell| "" }.join("") + ret << "" + elsif index == MAX_TABLE_ROWS + ret << "" + row_cells = extract_tags_table_cells(row) + ret << row_cells.map { |cell| "" }.join("") + ret << "" + end + end + + ret << "" + ret << "
#{cell}
#{cell}
...
" # End html table + end + + return ret +end + +# Return array of table columns from one row/header of a table. +def extract_tags_table_cells(text) + raise ArgumentError, "Expected String for text but was passed a #{text}.class" unless text.is_a?(String) + + # This pattern matches strings that: + # - Start with a non-pipe, non-whitespace character + # - Then contain zero or more non-pipe characters (can include internal spaces) + # - End with a non-pipe, non-whitespace character + # + # All leading/trailing whitespace is removed. + # + # Examples: + # "| H1 | H2".scan(/[^|\s][^|]*[^|\s]/) + # => ["H1", "H2"] + # + # "| ABC | DEF GHI |".scan(/[^|\s][^|]*[^|\s]/) + # => ["ABC", "DEF GHI"] # Note: internal space preserved + # + # "| Name | Value |".scan(/[^|\s][^|]*[^|\s]/) + # => ["Name", "Value"] # Leading/trailing spaces removed + text.scan(/[^|\s][^|]*[^|\s]/) +end + # Cleanup the tag text to be suitably displayed. def limit_table_rows(text) raise ArgumentError, "Expected String for text but was passed a #{text}.class" unless text.is_a?(String) @@ -1146,7 +1237,7 @@ def limit_table_rows(text) # This is the detection pattern for an entire table being tagged from the "tags.rb" AsciiDoctor backend. if text.end_with?("\n===") # Limit table size displayed. - truncate_after_newlines(text, 12) + truncate_after_newlines(text, MAX_TABLE_ROWS) else text end @@ -1181,7 +1272,7 @@ def count_parameters(defs) end # Convert newlines to
. -def html_convert_newlines(text) +def convert_newlines_to_html(text) raise ArgumentError, "Expected String for text but was passed a #{text}.class" unless text.is_a?(String) text.gsub(/\n/, '
')