Repository: jedib0t/go-pretty
Pull request #319 (2024-05-03 09:35:22)

table: html row automerge support
## Proposed Changes

Support AutoMerge across row entries in HTML rendering mode.

Thanks for a great library.


coveralls: ## Pull Request Test Coverage Report for [Build 8937042509](https://coveralls.io/builds/67284207)


### Details

* **20** of **20**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/67284207/badge)](https://coveralls.io/builds/67284207) |
| :-- | --: |
| Change from base [Build 8592698758](https://coveralls.io/builds/66765736): |  0.0% |
| Covered Lines: | 3555 |
| Relevant Lines: | 3555 |

---
##### 💛  - [Coveralls](https://coveralls.io)

jedib0t: I really appreciate your contribution. If you can add one more test with the above use-case, it will help assure its functionality for more than a simple use-case. Thanks!
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=319) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=319&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=319&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=319&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=319&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=319)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=319) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=319&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=319&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=319&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=319&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=319)



Patch:
diff --git a/table/render_html.go b/table/render_html.go
index fcea86b..decf4fa 100644
--- a/table/render_html.go
+++ b/table/render_html.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"html"
 	"strings"
+
+	"github.com/jedib0t/go-pretty/v6/text"
 )
 
 const (
@@ -113,9 +115,9 @@ func (t *Table) htmlRenderColumn(out *strings.Builder, colStr string) {
 	out.WriteString(colStr)
 }
 
-func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, colIdx int, hint renderHint) {
+func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, colIdx int, hint renderHint, alignOverride text.Align) {
 	// determine the HTML "align"/"valign" property values
-	align := t.getAlign(colIdx, hint).HTMLProperty()
+	align := alignOverride.HTMLProperty()
 	vAlign := t.getVAlign(colIdx, hint).HTMLProperty()
 	// determine the HTML "class" property values for the colors
 	class := t.getColumnColors(colIdx, hint).HTMLProperty()
@@ -158,11 +160,31 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
 			t.htmlRenderColumnAutoIndex(out, hint)
 		}
 
+		align := t.getAlign(colIdx, hint)
+		rowConfig := t.getRowConfig(hint)
+		extraColumnsRendered := 0
+		if rowConfig.AutoMerge && !hint.isSeparatorRow {
+			// get the real row to consider all lines in each column instead of just
+			// looking at the current "line"
+			rowUnwrapped := t.getRow(hint.rowNumber-1, hint)
+			for idx := colIdx + 1; idx < len(rowUnwrapped); idx++ {
+				if rowUnwrapped[colIdx] != rowUnwrapped[idx] {
+					break
+				}
+				align = rowConfig.getAutoMergeAlign()
+				extraColumnsRendered++
+			}
+		}
+
 		colStr, colTagName := t.htmlGetColStrAndTag(row, colIdx, hint)
 		// write the row
 		out.WriteString("    <")
 		out.WriteString(colTagName)
-		t.htmlRenderColumnAttributes(out, colIdx, hint)
+		t.htmlRenderColumnAttributes(out, colIdx, hint, align)
+		if extraColumnsRendered > 0 {
+			out.WriteString(" colspan=")
+			out.WriteString(fmt.Sprint(extraColumnsRendered + 1))
+		}
 		out.WriteString(">")
 		if len(colStr) == 0 {
 			out.WriteString(t.style.HTML.EmptyColumn)
@@ -172,6 +194,7 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
 		out.WriteString("</")
 		out.WriteString(colTagName)
 		out.WriteString(">\n")
+		colIdx += extraColumnsRendered
 	}
 	out.WriteString("  </tr>\n")
 }


Test patch:
diff --git a/table/render_html_test.go b/table/render_html_test.go
index ec507ee..ad2eae3 100644
--- a/table/render_html_test.go
+++ b/table/render_html_test.go
@@ -517,3 +517,88 @@ func TestTable_RenderHTML_Sorted(t *testing.T) {
   </tfoot>
 </table>`)
 }
+
+func TestTable_RenderHTML_RowAutoMerge(t *testing.T) {
+	t.Run("simple", func(t *testing.T) {
+		rcAutoMerge := RowConfig{AutoMerge: true}
+		tw := NewWriter()
+		tw.AppendHeader(Row{"A", "B"})
+		tw.AppendRow(Row{"Y", "Y"}, rcAutoMerge)
+		tw.AppendRow(Row{"Y", "N"}, rcAutoMerge)
+		compareOutput(t, tw.RenderHTML(), `
+<table class="go-pretty-table">
+  <thead>
+  <tr>
+    <th>A</th>
+    <th>B</th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td align="center" colspan=2>Y</td>
+  </tr>
+  <tr>
+    <td>Y</td>
+    <td>N</td>
+  </tr>
+  </tbody>
+</table>`)
+	})
+	t.Run("merged and unmerged entries", func(t *testing.T) {
+		rcAutoMerge := RowConfig{AutoMerge: true}
+		tw := NewWriter()
+		tw.AppendHeader(Row{"A", "B", "C", "D"})
+		tw.AppendRow(Row{"Y", "Y", "0", "1"}, rcAutoMerge)
+		tw.AppendRow(Row{"0", "Y", "Y", "1"}, rcAutoMerge)
+		tw.AppendRow(Row{"0", "1", "Y", "Y"}, rcAutoMerge)
+		tw.AppendRow(Row{"Y", "Y", "Y", "0"}, rcAutoMerge)
+		tw.AppendRow(Row{"0", "Y", "Y", "Y"}, rcAutoMerge)
+		tw.AppendRow(Row{"Y", "Y", "Y", "Y"}, rcAutoMerge)
+		tw.AppendRow(Row{"0", "1", "2", "3"}, rcAutoMerge)
+		compareOutput(t, tw.RenderHTML(), `
+<table class="go-pretty-table">
+  <thead>
+  <tr>
+    <th>A</th>
+    <th>B</th>
+    <th>C</th>
+    <th>D</th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td align="center" colspan=2>Y</td>
+    <td>0</td>
+    <td>1</td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td align="center" colspan=2>Y</td>
+    <td>1</td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td>1</td>
+    <td align="center" colspan=2>Y</td>
+  </tr>
+  <tr>
+    <td align="center" colspan=3>Y</td>
+    <td>0</td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td align="center" colspan=3>Y</td>
+  </tr>
+  <tr>
+    <td align="center" colspan=4>Y</td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td>1</td>
+    <td>2</td>
+    <td>3</td>
+  </tr>
+  </tbody>
+</table>`)
+	})
+}