Skip to content

Commit

Permalink
feat(parser): support attribute substitution in element attributes (b…
Browse files Browse the repository at this point in the history
…ytesparadise#799)

Support for standard "named" attributes (ie, with key/value, including
in single and double quotes), as well as on the `title` attribute. Also
works in other places such as image inline attributes.

Also, rename `doctitle` class to `title` for paragraph titles.
Also, removed more duplicate grammar rules related to attributes.

Fixes bytesparadise#604

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Nov 9, 2020
1 parent 6b43b68 commit 62ddfce
Show file tree
Hide file tree
Showing 20 changed files with 4,576 additions and 4,082 deletions.
14 changes: 8 additions & 6 deletions pkg/parser/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ var _ = Describe("attributes", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})
It("block image with double quoted alt and embedded quotes", func() {
source := "image::foo.png[ \"The Ascii\\\"Doctor\\\" Is In\" ]"
source := `image::foo.png[ "The Ascii\"Doctor\" Is In" ]`
expected := types.DraftDocument{
Elements: []interface{}{
types.ImageBlock{
Expand All @@ -115,12 +115,12 @@ var _ = Describe("attributes", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})
It("block image with double quoted alt extra whitespace", func() {
source := "image::foo.png[ \"This \\Backslash 2Spaced End Space \" ]"
source := `image::foo.png[ "This \Backslash 2Spaced End Space " ]`
expected := types.DraftDocument{
Elements: []interface{}{
types.ImageBlock{
Attributes: types.Attributes{
types.AttrImageAlt: `This \Backslash 2Spaced End Space `,
types.AttrImageAlt: `This \Backslash 2Spaced End Space `, // trailing space is retained
},
Location: types.Location{
Path: []interface{}{
Expand Down Expand Up @@ -156,7 +156,7 @@ var _ = Describe("attributes", func() {
Elements: []interface{}{
types.ImageBlock{
Attributes: types.Attributes{
types.AttrImageAlt: `This \Backslash 2Spaced End Space `,
types.AttrImageAlt: `This \Backslash 2Spaced End Space `, // trailing space within quotes is retained
},
Location: types.Location{
Path: []interface{}{
Expand All @@ -171,7 +171,7 @@ var _ = Describe("attributes", func() {
Expect(result).To(MatchDraftDocument(expected))
})
It("block image alt and named pair", func() {
source := "image::foo.png[\"Quoted, Here\", height=100]"
source := `image::foo.png["Quoted, Here", height=100]`
expected := types.DraftDocument{
Elements: []interface{}{
types.ImageBlock{
Expand All @@ -187,7 +187,9 @@ var _ = Describe("attributes", func() {
},
},
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
result, err := ParseDraftDocument(source)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(MatchDraftDocument(expected))
})
It("block image alt, width, height, and named pair", func() {
source := "image::foo.png[\"Quoted, Here\", 1, 2, height=100]"
Expand Down
3 changes: 1 addition & 2 deletions pkg/parser/cross_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ with some content linked to <<thetitle>>!`
Content: "with some content linked to ",
},
types.InternalCrossReference{
ID: "thetitle",
Label: "",
ID: "thetitle",
},
types.StringElement{
Content: "!",
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/delimited_block_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ foo
Elements: []interface{}{
types.ExampleBlock{
Attributes: types.Attributes{
types.AttrCaption: "a caption ", // trailing space is preserved
types.AttrCaption: "a caption ", // trailing space is retained
},
Elements: []interface{}{
types.Paragraph{
Expand Down
85 changes: 38 additions & 47 deletions pkg/parser/document_processing_apply_substitutions.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,23 @@ func applySubstitutions(elements []interface{}, attrs types.AttributesWithOverri
}
result := make([]interface{}, 0, len(elements))
for _, e := range elements {
log.Debugf("applying substitution on attributes of element of type '%T'", e)
if a, ok := e.(types.WithAttributesToSubstitute); ok {
attrs, err := applyAttributeSubstitutionsOnAttributes(a.AttributesToSubstitute(), attrs)
if err != nil {
return nil, err
}
e = a.ReplaceAttributes(attrs)
}
log.Debugf("applying substitutions on element of type '%T'", e)
var err error
switch e := e.(type) {
case types.WithElementSubstitution:
case types.WithNestedElementSubstitution:
subs, err := substitutionsFor(e)
if err != nil {
return nil, err
}
elements, err := applySubstitutionsOnElements(e.ElementsToReplace(), subs, attrs)
elements, err := applySubstitutionsOnElements(e.ElementsToSubstitute(), subs, attrs)
if err != nil {
return nil, err
}
Expand All @@ -76,7 +84,7 @@ func applySubstitutions(elements []interface{}, attrs types.AttributesWithOverri
if err != nil {
return nil, err
}
result = append(result, e.ReplaceLines(elements))
result = append(result, e.SubstituteLines(elements))
case types.MarkdownQuoteBlock: // slightly different since there is an extraction for the author attributions
e, err := applySubstitutionsOnMarkdownQuoteBlock(e, attrs)
if err != nil {
Expand Down Expand Up @@ -133,7 +141,7 @@ var substitutions = map[string]elementsSubstitution{
"none": substituteNone,
}

func substitutionsFor(block types.WithSubstitution) ([]elementsSubstitution, error) {
func substitutionsFor(block types.WithCustomSubstitutions) ([]elementsSubstitution, error) {
subs := funcs{}
for _, s := range block.SubstitutionsToApply() {
switch s {
Expand Down Expand Up @@ -197,51 +205,15 @@ func (f funcs) remove(other string) funcs {
return f
}

// func defaultSubstitutionsFor(block interface{}) []string {
// switch b := block.(type) {
// case types.ExampleBlock:
// return defaultExampleBlockSubstitutions
// case types.QuoteBlock:
// return defaultQuoteBlockSubstitutions
// case types.SidebarBlock:
// return defaultSidebarBlockSubstitutions
// case types.FencedBlock:
// return defaultFencedBlockSubstitutions
// case types.ListingBlock:
// return defaultListingBlockSubstitutions
// case types.VerseBlock:
// return defaultVerseBlockSubstitutions
// case types.LiteralBlock:
// return defaultLiteralBlockSubstitutions
// case types.PassthroughBlock:
// return defaultPassthroughBlockSubstitutions
// case types.Paragraph:
// // support for masquerading
// // treat 'Listing' paragraphs as verbatim blocks
// if k, exists := b.Attributes[types.AttrBlockKind]; exists {
// switch k {
// case types.Listing:
// return defaultListingBlockSubstitutions
// }
// }
// return defaultParagraphSubstitutions
// case types.Section:
// return defaultSectionSubstitutions
// default:
// log.Warnf("unsupported substitutions on block of type: '%T'", block)
// return nil
// }
// }

func applySubstitutionsOnElements(elements []interface{}, subs []elementsSubstitution, attrs types.AttributesWithOverrides) ([]interface{}, error) {
// var err error
// apply all the substitutions on blocks that need to be processed
// apply all the substitutions on elements that need to be processed
for i, element := range elements {
log.Debugf("applying substitution on element of type '%T'", element)
switch e := element.(type) {
// if the block contains a block...
case types.WithElementSubstitution:
lines, err := applySubstitutionsOnElements(e.ElementsToReplace(), subs, attrs)
case types.WithNestedElementSubstitution:
lines, err := applySubstitutionsOnElements(e.ElementsToSubstitute(), subs, attrs)
if err != nil {
return nil, err
}
Expand All @@ -251,7 +223,7 @@ func applySubstitutionsOnElements(elements []interface{}, subs []elementsSubstit
if err != nil {
return nil, err
}
elements[i] = e.ReplaceLines(lines)
elements[i] = e.SubstituteLines(lines)
default:
log.Debugf("nothing to substitute on element of type '%T'", element)
// do nothing
Expand Down Expand Up @@ -602,6 +574,25 @@ func applyAttributeSubstitutionsOnElements(elements []interface{}, attrs types.A
return result, nil
}

func applyAttributeSubstitutionsOnAttributes(attributes types.Attributes, attrs types.AttributesWithOverrides) (types.Attributes, error) {
for key, value := range attributes {
if value, ok := value.([]interface{}); ok {
value, err := applyAttributeSubstitutionsOnElements(value, attrs)
if err != nil {
return nil, err
}
if len(value) == 1 {
if v, ok := value[0].(types.StringElement); ok {
attributes[key] = v.Content
continue
}
}
attributes[key] = value
}
}
return attributes, nil
}

func applyAttributeSubstitutionsOnLines(lines [][]interface{}, attrs types.AttributesWithOverrides) ([][]interface{}, error) {
for i, line := range lines {
line, err := applyAttributeSubstitutionsOnElements(line, attrs)
Expand Down Expand Up @@ -634,8 +625,8 @@ func applyAttributeSubstitutionsOnElement(element interface{}, attrs types.Attri
}, nil
case types.CounterSubstitution:
return applyCounterSubstitution(e, attrs)
case types.WithElementsToReplace:
elmts, err := applyAttributeSubstitutionsOnElements(e.ElementsToReplace(), attrs)
case types.WithElementsToSubstitute:
elmts, err := applyAttributeSubstitutionsOnElements(e.ElementsToSubstitute(), attrs)
if err != nil {
return e, err
}
Expand All @@ -645,7 +636,7 @@ func applyAttributeSubstitutionsOnElement(element interface{}, attrs types.Attri
if err != nil {
return e, err
}
return e.ReplaceLines(lines), nil
return e.SubstituteLines(lines), nil
case types.ContinuedListItemElement:
e.Element, err = applyAttributeSubstitutionsOnElement(e.Element, attrs)
return e, err
Expand Down
Loading

0 comments on commit 62ddfce

Please sign in to comment.