diff --git a/hugolib/page.go b/hugolib/page.go index 4752d11f1..37bf528c7 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -39,8 +39,6 @@ import ( "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/parser/metadecoders" - "errors" - "github.com/gohugoio/hugo/parser/pageparser" "github.com/gohugoio/hugo/output" @@ -762,7 +760,7 @@ Loop: case it.IsEOF(): break Loop case it.IsError(): - err := fail(errors.New(it.ValStr(result.Input())), it) + err := fail(it.Err, it) currShortcode.err = err return err diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 1627acacb..b2f42ff1d 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -497,8 +497,6 @@ func (s *shortcodeHandler) renderShortcodesForPage(p *pageState, f output.Format return rendered, hasVariants, nil } -var errShortCodeIllegalState = errors.New("Illegal shortcode state") - func (s *shortcodeHandler) parseError(err error, input []byte, pos int) error { if s.p != nil { return s.p.parseError(err, input, pos) @@ -640,7 +638,7 @@ Loop: if params, ok := sc.params.(map[string]any); ok { params[currItem.ValStr(source)] = pt.Next().ValTyped(source) } else { - return sc, errShortCodeIllegalState + return sc, fmt.Errorf("%s: invalid state: invalid param type %T for shortcode %q, expected a map", errorPrefix, params, sc.name) } } } else { @@ -654,7 +652,7 @@ Loop: params = append(params, currItem.ValTyped(source)) sc.params = params } else { - return sc, errShortCodeIllegalState + return sc, fmt.Errorf("%s: invalid state: invalid param type %T for shortcode %q, expected a slice", errorPrefix, params, sc.name) } } } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 3f9190962..ec521729b 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -1055,3 +1055,35 @@ title: "p1" `) } + +// Issue 10236. +func TestShortcodeParamEscapedQuote(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- + +{{< figure src="/media/spf13.jpg" title="Steve \"Francia\"." >}} + +-- layouts/shortcodes/figure.html -- +Title: {{ .Get "title" | safeHTML }} +-- layouts/_default/single.html -- +{{ .Content }} +` + + b := NewIntegrationTestBuilder( + IntegrationTestConfig{ + T: t, + TxtarString: files, + Running: true, + Verbose: true, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", `Title: Steve "Francia".`) + +} diff --git a/parser/pageparser/pagelexer.go b/parser/pageparser/pagelexer.go index a7e6b6cd4..5f5d147e6 100644 --- a/parser/pageparser/pagelexer.go +++ b/parser/pageparser/pagelexer.go @@ -194,7 +194,12 @@ func (l *pageLexer) ignoreEscapesAndEmit(t ItemType, isString bool) { if i > k { segments = append(segments, lowHigh{k, i}) } - l.append(Item{Type: TypeIgnore, low: i, high: i + w}) + // See issue #10236. + // We don't send the backslash back to the client, + // which makes the end parsing simpler. + // This means that we cannot render the AST back to be + // exactly the same as the input, + // but that was also the situation before we introduced the issue in #10236. k = i + w } i += w diff --git a/parser/pageparser/pageparser_shortcode_test.go b/parser/pageparser/pageparser_shortcode_test.go index a95d55ef3..26d836e32 100644 --- a/parser/pageparser/pageparser_shortcode_test.go +++ b/parser/pageparser/pageparser_shortcode_test.go @@ -40,7 +40,6 @@ var ( tstParamFloat = nti(tScParam, "3.14") tstVal = nti(tScParamVal, "Hello World") tstText = nti(tText, "Hello World") - tstIgnoreEscape = nti(TypeIgnore, "\\") ) var shortCodeLexerTests = []lexerTest{ @@ -179,14 +178,14 @@ var shortCodeLexerTests = []lexerTest{ "escaped quotes inside nonescaped quotes", `{{< sc1 param1="Hello \"escaped\" World" >}}`, []typeText{ - tstLeftNoMD, tstSC1, tstParam1, tstIgnoreEscape, tstIgnoreEscape, nti(tScParamVal, `Hello "escaped" World`), tstRightNoMD, tstEOF, + tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, `Hello "escaped" World`), tstRightNoMD, tstEOF, }, }, { "escaped quotes inside nonescaped quotes in positional param", `{{< sc1 "Hello \"escaped\" World" >}}`, []typeText{ - tstLeftNoMD, tstSC1, tstIgnoreEscape, tstIgnoreEscape, nti(tScParam, `Hello "escaped" World`), tstRightNoMD, tstEOF, + tstLeftNoMD, tstSC1, nti(tScParam, `Hello "escaped" World`), tstRightNoMD, tstEOF, }, }, {"escaped raw string, named param", `{{< sc1 param1=` + `\` + "`" + "Hello World" + `\` + "`" + ` >}}`, []typeText{