Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
548 lines
12 KiB
548 lines
12 KiB
package ast |
|
|
|
import ( |
|
"fmt" |
|
"strings" |
|
|
|
textm "github.com/yuin/goldmark/text" |
|
"github.com/yuin/goldmark/util" |
|
) |
|
|
|
// A BaseInline struct implements the Node interface. |
|
type BaseInline struct { |
|
BaseNode |
|
} |
|
|
|
// Type implements Node.Type |
|
func (b *BaseInline) Type() NodeType { |
|
return TypeInline |
|
} |
|
|
|
// IsRaw implements Node.IsRaw |
|
func (b *BaseInline) IsRaw() bool { |
|
return false |
|
} |
|
|
|
// HasBlankPreviousLines implements Node.HasBlankPreviousLines. |
|
func (b *BaseInline) HasBlankPreviousLines() bool { |
|
panic("can not call with inline nodes.") |
|
} |
|
|
|
// SetBlankPreviousLines implements Node.SetBlankPreviousLines. |
|
func (b *BaseInline) SetBlankPreviousLines(v bool) { |
|
panic("can not call with inline nodes.") |
|
} |
|
|
|
// Lines implements Node.Lines |
|
func (b *BaseInline) Lines() *textm.Segments { |
|
panic("can not call with inline nodes.") |
|
} |
|
|
|
// SetLines implements Node.SetLines |
|
func (b *BaseInline) SetLines(v *textm.Segments) { |
|
panic("can not call with inline nodes.") |
|
} |
|
|
|
// A Text struct represents a textual content of the Markdown text. |
|
type Text struct { |
|
BaseInline |
|
// Segment is a position in a source text. |
|
Segment textm.Segment |
|
|
|
flags uint8 |
|
} |
|
|
|
const ( |
|
textSoftLineBreak = 1 << iota |
|
textHardLineBreak |
|
textRaw |
|
textCode |
|
) |
|
|
|
func textFlagsString(flags uint8) string { |
|
buf := []string{} |
|
if flags&textSoftLineBreak != 0 { |
|
buf = append(buf, "SoftLineBreak") |
|
} |
|
if flags&textHardLineBreak != 0 { |
|
buf = append(buf, "HardLineBreak") |
|
} |
|
if flags&textRaw != 0 { |
|
buf = append(buf, "Raw") |
|
} |
|
if flags&textCode != 0 { |
|
buf = append(buf, "Code") |
|
} |
|
return strings.Join(buf, ", ") |
|
} |
|
|
|
// Inline implements Inline.Inline. |
|
func (n *Text) Inline() { |
|
} |
|
|
|
// SoftLineBreak returns true if this node ends with a new line, |
|
// otherwise false. |
|
func (n *Text) SoftLineBreak() bool { |
|
return n.flags&textSoftLineBreak != 0 |
|
} |
|
|
|
// SetSoftLineBreak sets whether this node ends with a new line. |
|
func (n *Text) SetSoftLineBreak(v bool) { |
|
if v { |
|
n.flags |= textSoftLineBreak |
|
} else { |
|
n.flags = n.flags &^ textHardLineBreak |
|
} |
|
} |
|
|
|
// IsRaw returns true if this text should be rendered without unescaping |
|
// back slash escapes and resolving references. |
|
func (n *Text) IsRaw() bool { |
|
return n.flags&textRaw != 0 |
|
} |
|
|
|
// SetRaw sets whether this text should be rendered as raw contents. |
|
func (n *Text) SetRaw(v bool) { |
|
if v { |
|
n.flags |= textRaw |
|
} else { |
|
n.flags = n.flags &^ textRaw |
|
} |
|
} |
|
|
|
// HardLineBreak returns true if this node ends with a hard line break. |
|
// See https://spec.commonmark.org/0.29/#hard-line-breaks for details. |
|
func (n *Text) HardLineBreak() bool { |
|
return n.flags&textHardLineBreak != 0 |
|
} |
|
|
|
// SetHardLineBreak sets whether this node ends with a hard line break. |
|
func (n *Text) SetHardLineBreak(v bool) { |
|
if v { |
|
n.flags |= textHardLineBreak |
|
} else { |
|
n.flags = n.flags &^ textHardLineBreak |
|
} |
|
} |
|
|
|
// Merge merges a Node n into this node. |
|
// Merge returns true if the given node has been merged, otherwise false. |
|
func (n *Text) Merge(node Node, source []byte) bool { |
|
t, ok := node.(*Text) |
|
if !ok { |
|
return false |
|
} |
|
if n.Segment.Stop != t.Segment.Start || t.Segment.Padding != 0 || source[n.Segment.Stop-1] == '\n' || t.IsRaw() != n.IsRaw() { |
|
return false |
|
} |
|
n.Segment.Stop = t.Segment.Stop |
|
n.SetSoftLineBreak(t.SoftLineBreak()) |
|
n.SetHardLineBreak(t.HardLineBreak()) |
|
return true |
|
} |
|
|
|
// Text implements Node.Text. |
|
func (n *Text) Text(source []byte) []byte { |
|
return n.Segment.Value(source) |
|
} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *Text) Dump(source []byte, level int) { |
|
fs := textFlagsString(n.flags) |
|
if len(fs) != 0 { |
|
fs = "(" + fs + ")" |
|
} |
|
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Text(source)), "\n")) |
|
} |
|
|
|
// KindText is a NodeKind of the Text node. |
|
var KindText = NewNodeKind("Text") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *Text) Kind() NodeKind { |
|
return KindText |
|
} |
|
|
|
// NewText returns a new Text node. |
|
func NewText() *Text { |
|
return &Text{ |
|
BaseInline: BaseInline{}, |
|
} |
|
} |
|
|
|
// NewTextSegment returns a new Text node with the given source position. |
|
func NewTextSegment(v textm.Segment) *Text { |
|
return &Text{ |
|
BaseInline: BaseInline{}, |
|
Segment: v, |
|
} |
|
} |
|
|
|
// NewRawTextSegment returns a new Text node with the given source position. |
|
// The new node should be rendered as raw contents. |
|
func NewRawTextSegment(v textm.Segment) *Text { |
|
t := &Text{ |
|
BaseInline: BaseInline{}, |
|
Segment: v, |
|
} |
|
t.SetRaw(true) |
|
return t |
|
} |
|
|
|
// MergeOrAppendTextSegment merges a given s into the last child of the parent if |
|
// it can be merged, otherwise creates a new Text node and appends it to after current |
|
// last child. |
|
func MergeOrAppendTextSegment(parent Node, s textm.Segment) { |
|
last := parent.LastChild() |
|
t, ok := last.(*Text) |
|
if ok && t.Segment.Stop == s.Start && !t.SoftLineBreak() { |
|
t.Segment = t.Segment.WithStop(s.Stop) |
|
} else { |
|
parent.AppendChild(parent, NewTextSegment(s)) |
|
} |
|
} |
|
|
|
// MergeOrReplaceTextSegment merges a given s into a previous sibling of the node n |
|
// if a previous sibling of the node n is *Text, otherwise replaces Node n with s. |
|
func MergeOrReplaceTextSegment(parent Node, n Node, s textm.Segment) { |
|
prev := n.PreviousSibling() |
|
if t, ok := prev.(*Text); ok && t.Segment.Stop == s.Start && !t.SoftLineBreak() { |
|
t.Segment = t.Segment.WithStop(s.Stop) |
|
parent.RemoveChild(parent, n) |
|
} else { |
|
parent.ReplaceChild(parent, n, NewTextSegment(s)) |
|
} |
|
} |
|
|
|
// A String struct is a textual content that has a concrete value |
|
type String struct { |
|
BaseInline |
|
|
|
Value []byte |
|
flags uint8 |
|
} |
|
|
|
// Inline implements Inline.Inline. |
|
func (n *String) Inline() { |
|
} |
|
|
|
// IsRaw returns true if this text should be rendered without unescaping |
|
// back slash escapes and resolving references. |
|
func (n *String) IsRaw() bool { |
|
return n.flags&textRaw != 0 |
|
} |
|
|
|
// SetRaw sets whether this text should be rendered as raw contents. |
|
func (n *String) SetRaw(v bool) { |
|
if v { |
|
n.flags |= textRaw |
|
} else { |
|
n.flags = n.flags &^ textRaw |
|
} |
|
} |
|
|
|
// IsCode returns true if this text should be rendered without any |
|
// modifications. |
|
func (n *String) IsCode() bool { |
|
return n.flags&textCode != 0 |
|
} |
|
|
|
// SetCode sets whether this text should be rendered without any modifications. |
|
func (n *String) SetCode(v bool) { |
|
if v { |
|
n.flags |= textCode |
|
} else { |
|
n.flags = n.flags &^ textCode |
|
} |
|
} |
|
|
|
// Text implements Node.Text. |
|
func (n *String) Text(source []byte) []byte { |
|
return n.Value |
|
} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *String) Dump(source []byte, level int) { |
|
fs := textFlagsString(n.flags) |
|
if len(fs) != 0 { |
|
fs = "(" + fs + ")" |
|
} |
|
fmt.Printf("%sString%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Value), "\n")) |
|
} |
|
|
|
// KindString is a NodeKind of the String node. |
|
var KindString = NewNodeKind("String") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *String) Kind() NodeKind { |
|
return KindString |
|
} |
|
|
|
// NewString returns a new String node. |
|
func NewString(v []byte) *String { |
|
return &String{ |
|
Value: v, |
|
} |
|
} |
|
|
|
// A CodeSpan struct represents a code span of Markdown text. |
|
type CodeSpan struct { |
|
BaseInline |
|
} |
|
|
|
// Inline implements Inline.Inline . |
|
func (n *CodeSpan) Inline() { |
|
} |
|
|
|
// IsBlank returns true if this node consists of spaces, otherwise false. |
|
func (n *CodeSpan) IsBlank(source []byte) bool { |
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() { |
|
text := c.(*Text).Segment |
|
if !util.IsBlank(text.Value(source)) { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
// Dump implements Node.Dump |
|
func (n *CodeSpan) Dump(source []byte, level int) { |
|
DumpHelper(n, source, level, nil, nil) |
|
} |
|
|
|
// KindCodeSpan is a NodeKind of the CodeSpan node. |
|
var KindCodeSpan = NewNodeKind("CodeSpan") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *CodeSpan) Kind() NodeKind { |
|
return KindCodeSpan |
|
} |
|
|
|
// NewCodeSpan returns a new CodeSpan node. |
|
func NewCodeSpan() *CodeSpan { |
|
return &CodeSpan{ |
|
BaseInline: BaseInline{}, |
|
} |
|
} |
|
|
|
// An Emphasis struct represents an emphasis of Markdown text. |
|
type Emphasis struct { |
|
BaseInline |
|
|
|
// Level is a level of the emphasis. |
|
Level int |
|
} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *Emphasis) Dump(source []byte, level int) { |
|
m := map[string]string{ |
|
"Level": fmt.Sprintf("%v", n.Level), |
|
} |
|
DumpHelper(n, source, level, m, nil) |
|
} |
|
|
|
// KindEmphasis is a NodeKind of the Emphasis node. |
|
var KindEmphasis = NewNodeKind("Emphasis") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *Emphasis) Kind() NodeKind { |
|
return KindEmphasis |
|
} |
|
|
|
// NewEmphasis returns a new Emphasis node with the given level. |
|
func NewEmphasis(level int) *Emphasis { |
|
return &Emphasis{ |
|
BaseInline: BaseInline{}, |
|
Level: level, |
|
} |
|
} |
|
|
|
type baseLink struct { |
|
BaseInline |
|
|
|
// Destination is a destination(URL) of this link. |
|
Destination []byte |
|
|
|
// Title is a title of this link. |
|
Title []byte |
|
} |
|
|
|
// Inline implements Inline.Inline. |
|
func (n *baseLink) Inline() { |
|
} |
|
|
|
// A Link struct represents a link of the Markdown text. |
|
type Link struct { |
|
baseLink |
|
} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *Link) Dump(source []byte, level int) { |
|
m := map[string]string{} |
|
m["Destination"] = string(n.Destination) |
|
m["Title"] = string(n.Title) |
|
DumpHelper(n, source, level, m, nil) |
|
} |
|
|
|
// KindLink is a NodeKind of the Link node. |
|
var KindLink = NewNodeKind("Link") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *Link) Kind() NodeKind { |
|
return KindLink |
|
} |
|
|
|
// NewLink returns a new Link node. |
|
func NewLink() *Link { |
|
c := &Link{ |
|
baseLink: baseLink{ |
|
BaseInline: BaseInline{}, |
|
}, |
|
} |
|
return c |
|
} |
|
|
|
// An Image struct represents an image of the Markdown text. |
|
type Image struct { |
|
baseLink |
|
} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *Image) Dump(source []byte, level int) { |
|
m := map[string]string{} |
|
m["Destination"] = string(n.Destination) |
|
m["Title"] = string(n.Title) |
|
DumpHelper(n, source, level, m, nil) |
|
} |
|
|
|
// KindImage is a NodeKind of the Image node. |
|
var KindImage = NewNodeKind("Image") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *Image) Kind() NodeKind { |
|
return KindImage |
|
} |
|
|
|
// NewImage returns a new Image node. |
|
func NewImage(link *Link) *Image { |
|
c := &Image{ |
|
baseLink: baseLink{ |
|
BaseInline: BaseInline{}, |
|
}, |
|
} |
|
c.Destination = link.Destination |
|
c.Title = link.Title |
|
for n := link.FirstChild(); n != nil; { |
|
next := n.NextSibling() |
|
link.RemoveChild(link, n) |
|
c.AppendChild(c, n) |
|
n = next |
|
} |
|
|
|
return c |
|
} |
|
|
|
// AutoLinkType defines kind of auto links. |
|
type AutoLinkType int |
|
|
|
const ( |
|
// AutoLinkEmail indicates that an autolink is an email address. |
|
AutoLinkEmail AutoLinkType = iota + 1 |
|
// AutoLinkURL indicates that an autolink is a generic URL. |
|
AutoLinkURL |
|
) |
|
|
|
// An AutoLink struct represents an autolink of the Markdown text. |
|
type AutoLink struct { |
|
BaseInline |
|
// Type is a type of this autolink. |
|
AutoLinkType AutoLinkType |
|
|
|
// Protocol specified a protocol of the link. |
|
Protocol []byte |
|
|
|
value *Text |
|
} |
|
|
|
// Inline implements Inline.Inline. |
|
func (n *AutoLink) Inline() {} |
|
|
|
// Dump implements Node.Dump |
|
func (n *AutoLink) Dump(source []byte, level int) { |
|
segment := n.value.Segment |
|
m := map[string]string{ |
|
"Value": string(segment.Value(source)), |
|
} |
|
DumpHelper(n, source, level, m, nil) |
|
} |
|
|
|
// KindAutoLink is a NodeKind of the AutoLink node. |
|
var KindAutoLink = NewNodeKind("AutoLink") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *AutoLink) Kind() NodeKind { |
|
return KindAutoLink |
|
} |
|
|
|
// URL returns an url of this node. |
|
func (n *AutoLink) URL(source []byte) []byte { |
|
if n.Protocol != nil { |
|
s := n.value.Segment |
|
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3) |
|
ret = append(ret, n.Protocol...) |
|
ret = append(ret, ':', '/', '/') |
|
ret = append(ret, n.value.Text(source)...) |
|
return ret |
|
} |
|
return n.value.Text(source) |
|
} |
|
|
|
// Label returns a label of this node. |
|
func (n *AutoLink) Label(source []byte) []byte { |
|
return n.value.Text(source) |
|
} |
|
|
|
// NewAutoLink returns a new AutoLink node. |
|
func NewAutoLink(typ AutoLinkType, value *Text) *AutoLink { |
|
return &AutoLink{ |
|
BaseInline: BaseInline{}, |
|
value: value, |
|
AutoLinkType: typ, |
|
} |
|
} |
|
|
|
// A RawHTML struct represents an inline raw HTML of the Markdown text. |
|
type RawHTML struct { |
|
BaseInline |
|
Segments *textm.Segments |
|
} |
|
|
|
// Inline implements Inline.Inline. |
|
func (n *RawHTML) Inline() {} |
|
|
|
// Dump implements Node.Dump. |
|
func (n *RawHTML) Dump(source []byte, level int) { |
|
m := map[string]string{} |
|
t := []string{} |
|
for i := 0; i < n.Segments.Len(); i++ { |
|
segment := n.Segments.At(i) |
|
t = append(t, string(segment.Value(source))) |
|
} |
|
m["RawText"] = strings.Join(t, "") |
|
DumpHelper(n, source, level, m, nil) |
|
} |
|
|
|
// KindRawHTML is a NodeKind of the RawHTML node. |
|
var KindRawHTML = NewNodeKind("RawHTML") |
|
|
|
// Kind implements Node.Kind. |
|
func (n *RawHTML) Kind() NodeKind { |
|
return KindRawHTML |
|
} |
|
|
|
// NewRawHTML returns a new RawHTML node. |
|
func NewRawHTML() *RawHTML { |
|
return &RawHTML{ |
|
Segments: textm.NewSegments(), |
|
} |
|
}
|
|
|