mirror of
				https://github.com/kovetskiy/mark.git
				synced 2025-11-04 06:17:36 +08:00 
			
		
		
		
	feat: Support page inclusion macro
Also generalize the ac_tag_parser a bit and support <ri:* /> tags as well
This commit is contained in:
		
							parent
							
								
									943a356508
								
							
						
					
					
						commit
						1285947ab3
					
				@ -371,6 +371,10 @@ By default, mark provides several built-in templates and macros:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  See: https://confluence.atlassian.com/doc/blog-posts-macro-139470.html
 | 
					  See: https://confluence.atlassian.com/doc/blog-posts-macro-139470.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* template: `ac:include` to include a page
 | 
				
			||||||
 | 
					  - Page: the page to be included
 | 
				
			||||||
 | 
					  - Space: the space the page is in (optional, otherwise same space)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* macro `@{...}` to mention user by name specified in the braces.
 | 
					* macro `@{...}` to mention user by name specified in the braces.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Template & Macros Usecases
 | 
					## Template & Macros Usecases
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cparser "github.com/kovetskiy/mark/pkg/mark/parser"
 | 
				
			||||||
	"github.com/kovetskiy/mark/pkg/mark/stdlib"
 | 
						"github.com/kovetskiy/mark/pkg/mark/stdlib"
 | 
				
			||||||
	"github.com/reconquest/pkg/log"
 | 
						"github.com/reconquest/pkg/log"
 | 
				
			||||||
	"github.com/yuin/goldmark"
 | 
						"github.com/yuin/goldmark"
 | 
				
			||||||
@ -450,7 +451,7 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string {
 | 
				
			|||||||
	converter.Parser().AddOptions(parser.WithInlineParsers(
 | 
						converter.Parser().AddOptions(parser.WithInlineParsers(
 | 
				
			||||||
		// Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse
 | 
							// Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse
 | 
				
			||||||
		// the <ac:*/> tags.
 | 
							// the <ac:*/> tags.
 | 
				
			||||||
		util.Prioritized(NewACTagParser(), 199),
 | 
							util.Prioritized(cparser.NewConfluenceTagParser(), 199),
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	converter.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
						converter.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package mark
 | 
					package parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
@ -9,25 +9,25 @@ import (
 | 
				
			|||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewACTagParser returns an inline parser that parses <ac:* /> tags to ensure that Confluence specific tags are parsed
 | 
					// NewConfluenceTagParser returns an inline parser that parses <ac:* /> and <ri:* /> tags to ensure that Confluence specific tags are parsed
 | 
				
			||||||
// as ast.KindRawHtml so they are not escaped at render time. The parser must be registered with a higher priority
 | 
					// as ast.KindRawHtml so they are not escaped at render time. The parser must be registered with a higher priority
 | 
				
			||||||
// than goldmark's linkParser. Otherwise, the linkParser would parse the <ac:* /> tags.
 | 
					// than goldmark's linkParser. Otherwise, the linkParser would parse the <ac:* /> tags.
 | 
				
			||||||
func NewACTagParser() parser.InlineParser {
 | 
					func NewConfluenceTagParser() parser.InlineParser {
 | 
				
			||||||
	return &acTagParser{}
 | 
						return &confluenceTagParser{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ parser.InlineParser = (*acTagParser)(nil)
 | 
					var _ parser.InlineParser = (*confluenceTagParser)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// acTagParser is a stripped down version of goldmark's rawHTMLParser.
 | 
					// confluenceTagParser is a stripped down version of goldmark's rawHTMLParser.
 | 
				
			||||||
// See: https://github.com/yuin/goldmark/blob/master/parser/raw_html.go
 | 
					// See: https://github.com/yuin/goldmark/blob/master/parser/raw_html.go
 | 
				
			||||||
type acTagParser struct {
 | 
					type confluenceTagParser struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *acTagParser) Trigger() []byte {
 | 
					func (s *confluenceTagParser) Trigger() []byte {
 | 
				
			||||||
	return []byte{'<'}
 | 
						return []byte{'<'}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *acTagParser) Parse(_ ast.Node, block text.Reader, pc parser.Context) ast.Node {
 | 
					func (s *confluenceTagParser) Parse(_ ast.Node, block text.Reader, pc parser.Context) ast.Node {
 | 
				
			||||||
	line, _ := block.PeekLine()
 | 
						line, _ := block.PeekLine()
 | 
				
			||||||
	if len(line) > 1 && util.IsAlphaNumeric(line[1]) {
 | 
						if len(line) > 1 && util.IsAlphaNumeric(line[1]) {
 | 
				
			||||||
		return s.parseMultiLineRegexp(openTagRegexp, block, pc)
 | 
							return s.parseMultiLineRegexp(openTagRegexp, block, pc)
 | 
				
			||||||
@ -48,15 +48,15 @@ var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
 | 
					var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Only match <ac:*/> tags
 | 
					// Only match <ac:*/> and <ri:*/> tags
 | 
				
			||||||
var openTagRegexp = regexp.MustCompile("^<ac:" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
 | 
					var openTagRegexp = regexp.MustCompile("^<(ac|ri):" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
 | 
				
			||||||
var closeTagRegexp = regexp.MustCompile("^</ac:" + tagnamePattern + `\s*>`)
 | 
					var closeTagRegexp = regexp.MustCompile("^</ac:" + tagnamePattern + `\s*>`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var openCDATA = []byte("<![CDATA[")
 | 
					var openCDATA = []byte("<![CDATA[")
 | 
				
			||||||
var closeCDATA = []byte("]]>")
 | 
					var closeCDATA = []byte("]]>")
 | 
				
			||||||
var closeDecl = []byte(">")
 | 
					var closeDecl = []byte(">")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *acTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Context) ast.Node {
 | 
					func (s *confluenceTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Context) ast.Node {
 | 
				
			||||||
	savedLine, savedSegment := block.Position()
 | 
						savedLine, savedSegment := block.Position()
 | 
				
			||||||
	node := ast.NewRawHTML()
 | 
						node := ast.NewRawHTML()
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
@ -77,7 +77,7 @@ func (s *acTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Cont
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *acTagParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, _ parser.Context) ast.Node {
 | 
					func (s *confluenceTagParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, _ parser.Context) ast.Node {
 | 
				
			||||||
	sline, ssegment := block.Position()
 | 
						sline, ssegment := block.Position()
 | 
				
			||||||
	if block.Match(reg) {
 | 
						if block.Match(reg) {
 | 
				
			||||||
		node := ast.NewRawHTML()
 | 
							node := ast.NewRawHTML()
 | 
				
			||||||
@ -261,6 +261,18 @@ func templates(api *confluence.API) (*template.Template, error) {
 | 
				
			|||||||
			`</ac:structured-macro>{{printf "\n"}}`,
 | 
								`</ac:structured-macro>{{printf "\n"}}`,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* https://confluence.atlassian.com/conf59/include-page-macro-792499125.html */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							`ac:include`: text(
 | 
				
			||||||
 | 
								`<ac:structured-macro ac:name="include">{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`<ac:parameter ac:name="">{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`<ac:link>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`<ri:page ri:content-title="{{ .Page }}" {{if .Space }}ri:space-key="{{ .Space }}"{{end}}/>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`</ac:link>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`</ac:parameter>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`</ac:structured-macro>{{printf "\n"}}`,
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO(seletskiy): more templates here
 | 
							// TODO(seletskiy): more templates here
 | 
				
			||||||
	} {
 | 
						} {
 | 
				
			||||||
		templates, err = templates.New(name).Parse(body)
 | 
							templates, err = templates.New(name).Parse(body)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user