forked from bytesparadise/libasciidoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
renderer.go
148 lines (140 loc) · 4.71 KB
/
renderer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package html5
import (
"bytes"
"io"
"reflect"
"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/types"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// Render renders the given document in HTML and writes the result in the given `writer`
func Render(ctx *renderer.Context, output io.Writer) (map[string]interface{}, error) {
return renderDocument(ctx, output)
}
type rendererFunc func(*renderer.Context, interface{}) ([]byte, error)
func renderElements(ctx *renderer.Context, elements []interface{}) ([]byte, error) {
log.Debugf("rendered %d element(s)...", len(elements))
buff := bytes.NewBuffer(nil)
hasContent := false
for _, element := range elements {
renderedElement, err := renderElement(ctx, element)
if err != nil {
return nil, errors.Wrapf(err, "unable to render an element")
}
// insert new line if there's already some content
if hasContent && len(renderedElement) > 0 {
buff.WriteString("\n")
}
buff.Write(renderedElement)
if len(renderedElement) > 0 {
hasContent = true
}
}
// log.Debugf("rendered elements: '%s'", buff.String())
return buff.Bytes(), nil
}
func renderElement(ctx *renderer.Context, element interface{}) ([]byte, error) {
// log.Debugf("rendering element of type `%T`", element)
switch e := element.(type) {
case types.TableOfContentsMacro:
return renderTableOfContents(ctx, e)
case types.Section:
return renderSection(ctx, e)
case types.Preamble:
return renderPreamble(ctx, e)
case types.BlankLine:
return renderBlankLine(ctx, e)
case types.LabeledList:
return renderLabeledList(ctx, e)
case types.OrderedList:
return renderOrderedList(ctx, e)
case types.UnorderedList:
return renderUnorderedList(ctx, e)
case types.Paragraph:
return renderParagraph(ctx, e)
case types.CrossReference:
return renderCrossReference(ctx, e)
case types.QuotedText:
return renderQuotedText(ctx, e)
case types.Passthrough:
return renderPassthrough(ctx, e)
case types.ImageBlock:
return renderImageBlock(ctx, e)
case types.InlineImage:
return renderInlineImage(ctx, e)
case types.DelimitedBlock:
return renderDelimitedBlock(ctx, e)
case types.Table:
return renderTable(ctx, e)
case types.LiteralBlock:
return renderLiteralBlock(ctx, e)
case types.InlineElements:
return renderLine(ctx, e, renderElement)
case []interface{}:
return renderElements(ctx, e)
case types.InlineLink:
return renderLink(ctx, e)
case types.StringElement:
return renderStringElement(ctx, e)
case types.Footnote:
return renderFootnote(ctx, e)
case types.DocumentAttributeDeclaration:
// 'process' function do not return any rendered content, but may return an error
return nil, processAttributeDeclaration(ctx, e)
case types.DocumentAttributeReset:
// 'process' function do not return any rendered content, but may return an error
return nil, processAttributeReset(ctx, e)
case types.LineBreak:
return renderLineBreak()
case types.SingleLineComment:
return nil, nil // nothing to do
default:
return nil, errors.Errorf("unsupported type of element: %T", element)
}
}
func renderPlainString(ctx *renderer.Context, element interface{}) ([]byte, error) {
log.Debugf("rendering plain string for element of type %T", element)
switch element := element.(type) {
case types.SectionTitle:
return renderPlainString(ctx, element.Elements)
case types.QuotedText:
return renderPlainString(ctx, element.Elements)
case types.InlineImage:
return []byte(element.Attributes.GetAsString(types.AttrImageAlt)), nil
case types.InlineLink:
return []byte(element.Text()), nil
case types.BlankLine:
return []byte("\n\n"), nil
case types.StringElement:
return []byte(element.Content), nil
case types.Paragraph:
return renderLines(ctx, element.Lines, renderPlainString, false)
case types.InlineElements:
return renderLine(ctx, element, renderPlainString)
case []types.InlineElements:
return renderLines(ctx, element, renderPlainString, false)
default:
return nil, errors.Errorf("unable to render plain string for element of type '%T'", element)
}
}
// includeNewline returns an "\n" sequence if the given index is NOT the last entry in the given description lines, empty string otherwise.
// also, it ignores the element if it is a blankline, depending on the context
func includeNewline(ctx renderer.Context, index int, content interface{}) string {
switch reflect.TypeOf(content).Kind() {
case reflect.Slice, reflect.Array:
s := reflect.ValueOf(content)
if _, match := s.Index(index).Interface().(types.BlankLine); match {
if ctx.IncludeBlankLine() {
return "\n"
}
return ""
}
if index < s.Len()-1 {
return "\n"
}
default:
log.Warnf("content of type '%T' is not an array or a slice", content)
}
return ""
}