Skip to content

Commit bbc8983

Browse files
Fix figcaption parsing error and refactor figure processing to use native hype parser patterns (#44)
* fix caption parsing * more fixes * Update README.md with latest Hype changes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 6de54e5 commit bbc8983

File tree

7 files changed

+126
-36
lines changed

7 files changed

+126
-36
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ $ go run .
127127
Hello World
128128

129129
--------------------------------------------------------------------------------
130-
Go Version: go1.24.2
130+
Go Version: go1.24.4
131131

132132
```
133133

@@ -160,7 +160,7 @@ $ go run .
160160
Hello World
161161

162162
--------------------------------------------------------------------------------
163-
Go Version: go1.24.2
163+
Go Version: go1.24.4
164164

165165
```
166166

@@ -189,7 +189,7 @@ $ go run .
189189
Hello World
190190

191191
--------------------------------------------------------------------------------
192-
Go Version: go1.24.2
192+
Go Version: go1.24.4
193193

194194
```
195195

@@ -219,7 +219,7 @@ $ go run .
219219
./main.go:7:6: undefined: fmt.Prin
220220

221221
--------------------------------------------------------------------------------
222-
Go Version: go1.24.2
222+
Go Version: go1.24.4
223223

224224
```
225225

@@ -256,7 +256,7 @@ type Context interface{ ... }
256256
func WithoutCancel(parent Context) Context
257257

258258
--------------------------------------------------------------------------------
259-
Go Version: go1.24.2
259+
Go Version: go1.24.4
260260

261261
```
262262
@@ -279,7 +279,7 @@ func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
279279
call cancel as soon as the operations running in this Context complete.
280280

281281
--------------------------------------------------------------------------------
282-
Go Version: go1.24.2
282+
Go Version: go1.24.4
283283

284284
```
285285

figcaption_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package hype
22

3-
import "testing"
3+
import (
4+
"context"
5+
"strings"
6+
"testing"
7+
)
48

59
func Test_Figcaption_MarshalJSON(t *testing.T) {
610
t.Parallel()
@@ -11,5 +15,45 @@ func Test_Figcaption_MarshalJSON(t *testing.T) {
1115
fig.Nodes = append(fig.Nodes, Text("This is a caption"))
1216

1317
testJSON(t, "figcaption", fig)
18+
}
19+
20+
// Test that verifies the fix for figcaption elements being lost when mixed with <go> elements
21+
func Test_Figcaption_With_Go_Elements(t *testing.T) {
22+
t.Parallel()
23+
24+
// This structure was previously failing with "execute error: no figcaption"
25+
input := `<figure id="ticker" type="listing">
26+
<go doc="time.Ticker"></go>
27+
<figcaption>The <godoc>time#Ticker</godoc> function.</figcaption>
28+
</figure>`
29+
30+
p := testParser(t, "")
31+
32+
doc, err := p.Parse(strings.NewReader(input))
33+
if err != nil {
34+
t.Fatalf("unexpected error during parsing: %v", err)
35+
}
1436

37+
// Execute the document - this should now work without the "no figcaption" error
38+
ctx := context.Background()
39+
err = doc.Execute(ctx)
40+
if err != nil {
41+
t.Fatalf("unexpected error during execution: %v", err)
42+
}
43+
44+
// Verify that the figcaption is properly parsed and available
45+
figures := ByType[*Figure](doc.Nodes)
46+
if len(figures) == 0 {
47+
t.Fatal("no figures found")
48+
}
49+
50+
fig := figures[0]
51+
figcaptions := ByType[*Figcaption](fig.Nodes)
52+
if len(figcaptions) == 0 {
53+
t.Fatal("no figcaption found in figure - the fix didn't work")
54+
}
55+
56+
if len(figcaptions) != 1 {
57+
t.Fatalf("expected 1 figcaption, got %d", len(figcaptions))
58+
}
1559
}

figure.go

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package hype
22

33
import (
44
"encoding/json"
5-
"errors"
65
"fmt"
76
"strings"
87

98
"github.com/gobuffalo/flect"
9+
"golang.org/x/net/html"
1010
)
1111

1212
type Figure struct {
@@ -108,37 +108,85 @@ func NewFigure(p *Parser, el *Element) (*Figure, error) {
108108
f.style = style
109109
}
110110

111+
// Check if we need to process any markdown content (like ``` code blocks)
112+
// If the figure content contains raw markdown that wasn't preprocessed,
113+
// we need to process it while preserving all elements
111114
body := f.Nodes.String()
112-
body = strings.TrimSpace(body)
115+
if strings.Contains(body, "```") {
116+
// Process the content through markdown preprocessing but preserve
117+
// the structure by re-parsing the result
118+
nodes, err := f.processMarkdownContent(p, body)
119+
if err != nil {
120+
return nil, f.WrapErr(err)
121+
}
122+
f.Nodes = nodes
123+
}
113124

114-
if len(body) == 0 {
115-
return f, nil
125+
return f, nil
126+
}
127+
128+
// processMarkdownContent processes figure content that contains markdown syntax
129+
// while preserving all elements including figcaption
130+
func (f *Figure) processMarkdownContent(p *Parser, body string) (Nodes, error) {
131+
// Create a sub-parser with pages disabled to avoid wrapping figure content in <page> tags
132+
subParser, err := p.Sub(".")
133+
if err != nil {
134+
return nil, err
116135
}
136+
subParser.DisablePages = true
117137

118-
p2, err := p.Sub(".")
138+
// Run the content through the preprocessing pipeline to handle markdown syntax
139+
r := strings.NewReader(body)
140+
141+
// Apply preprocessing (including markdown conversion) with pages disabled
142+
processedReader, err := subParser.PreParsers.PreParse(subParser, r)
119143
if err != nil {
120-
return nil, f.WrapErr(err)
144+
return nil, err
121145
}
122146

123-
nodes, err := p2.ParseFragment(strings.NewReader(body))
147+
// Parse the preprocessed content as HTML
148+
htmlDoc, err := html.Parse(processedReader)
124149
if err != nil {
125-
if !errors.Is(err, ErrNilFigure) {
126-
return nil, f.WrapErr(err)
127-
}
150+
return nil, err
128151
}
129152

130-
pages := ByType[*Page](nodes)
131-
if len(pages) == 0 {
132-
f.Nodes = nodes
153+
// Extract the body content (preprocessing wraps content in <html><head></head><body>)
154+
var bodyElement *html.Node
155+
var findBody func(*html.Node)
156+
findBody = func(node *html.Node) {
157+
if node.Type == html.ElementNode && node.Data == "body" {
158+
bodyElement = node
159+
return
160+
}
161+
for child := node.FirstChild; child != nil; child = child.NextSibling {
162+
findBody(child)
163+
if bodyElement != nil {
164+
return
165+
}
166+
}
167+
}
168+
findBody(htmlDoc)
133169

134-
return f, nil
170+
if bodyElement == nil {
171+
return nil, fmt.Errorf("could not find body element after preprocessing")
135172
}
136173

137-
page := pages[0]
174+
// Parse each child of the body as a hype node
175+
var nodes Nodes
176+
for child := bodyElement.FirstChild; child != nil; child = child.NextSibling {
177+
// Skip pure whitespace text nodes for cleaner output
178+
if child.Type == html.TextNode && strings.TrimSpace(child.Data) == "" {
179+
continue
180+
}
138181

139-
f.Nodes = page.Nodes
182+
node, err := subParser.ParseHTMLNode(child, f)
183+
if err != nil {
184+
return nil, err
185+
}
186+
nodes = append(nodes, node)
187+
}
140188

141-
return f, nil
189+
return nodes, nil
142190
}
143191

144192
func NewFigureNodes(p *Parser, el *Element) (Nodes, error) {

testdata/auto/refs/fenced/hype.gold

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
<p>In C#, <ref id="figure-1-1"><a href="#figure-1-1">Figure 1.1</a></ref>, you declare that you are using the <code>Performer</code> interface by using the <code>:</code> operator after the class name and listing the interfaces you want to use.</p>
77

8-
<figure id="figure-1-1">
9-
<pre><code class="language-c#" language="c#">interface Performer {
8+
<figure id="figure-1-1"><pre><code class="language-c#" language="c#">interface Performer {
109
void Perform();
1110
}
1211

@@ -15,28 +14,21 @@ class Musician : Performer {
1514
public void Perform() {}
1615
}
1716

18-
</code></pre>
19-
20-
<figcaption><em class="figure-name">Figure 1.1:</em> Example C# implementation of the <code>Performer</code> interface.</figcaption>
21-
</figure>
17+
</code></pre><figcaption><em class="figure-name">Figure 1.1:</em> Example C# implementation of the <code>Performer</code> interface.</figcaption></figure>
2218

2319
<p>In Java, <ref id="figure-1-2"><a href="#figure-1-2">Figure 1.2</a></ref>, you use the <code>implements</code> keyword after the class name to tell the compiler that your type wants to implement the <code>Performer</code> interface.</p>
2420

2521
<div>
2622

27-
<figure id="figure-1-2">
28-
<pre><code class="language-java" language="java">interface Performer {
23+
<figure id="figure-1-2"><pre><code class="language-java" language="java">interface Performer {
2924
void Perform();
3025
}
3126

3227
// explicitly implements Performer
3328
class Musician implements Performer {
3429
void Perform() {}
3530
}
36-
</code></pre>
37-
38-
<figcaption><em class="figure-name">Figure 1.2:</em> Example Java implementation of <code>Performer</code> interface.</figcaption>
39-
</figure>
31+
</code></pre><figcaption><em class="figure-name">Figure 1.2:</em> Example Java implementation of <code>Performer</code> interface.</figcaption></figure>
4032

4133
</div>
4234
</page>

testdata/auto/refs/images/hype.gold

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h1>Image in a figure</h1>
33

44
<figure id="figure-1-1">
5+
56
<img alt="1" src="assets/nodes.svg"></img>
67

78
<img alt="2" src="assets/nodes.svg"></img>
@@ -11,6 +12,7 @@
1112
Hello</code></pre></cmd>
1213

1314
<figcaption><em class="figure-name">Figure 1.1:</em> Image caption</figcaption>
15+
1416
</figure>
1517
</page>
1618
</body></html>

testdata/auto/refs/includes/hype.gold

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
<p>In <ref id="figure-1-1"><a href="#figure-1-1">Figure 1.1</a></ref> we see the reference.</p>
55

66
<figure id="figure-1-1">
7+
78
<pre><code class="language-go" language="go" src="src/greet/main.go#example">fmt.Println("Hello, World!")</code></pre>
89

910
<img src="assets/foo.png"></img>
1011

1112
<figcaption><em class="figure-name">Figure 1.1:</em> Optional caption</figcaption>
13+
1214
</figure>
1315
</page>
1416
<page>

testdata/auto/refs/simple/hype.gold

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
<p>In <ref id="figure-1-1"><a href="#figure-1-1">Figure 1.1</a></ref> we see the reference.</p>
55

66
<figure id="figure-1-1">
7+
78
<pre><code class="language-go" language="go" src="src/greet/main.go#example">fmt.Println("Hello, World!")</code></pre>
89

910
<img src="assets/foo.png"></img>
1011

1112
<figcaption><em class="figure-name">Figure 1.1:</em> Optional caption</figcaption>
13+
1214
</figure>
1315

1416
<p>Some more text that references <ref id="figure-1-1"><a href="#figure-1-1">Figure 1.1</a></ref>.</p>

0 commit comments

Comments
 (0)