1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/dl88250-lute

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
h2m.go 42 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Daniel Отправлено 01.07.2024 11:08 e06da47
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
// Lute - 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript
// Copyright (c) 2019-present, b3log.org
//
// Lute is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
package lute
import (
"bytes"
"path"
"strings"
"unicode"
"github.com/88250/lute/ast"
"github.com/88250/lute/editor"
"github.com/88250/lute/html"
"github.com/88250/lute/html/atom"
"github.com/88250/lute/lex"
"github.com/88250/lute/parse"
"github.com/88250/lute/render"
"github.com/88250/lute/util"
)
// HTML2Markdown 将 HTML 转换为 Markdown。
func (lute *Lute) HTML2Markdown(htmlStr string) (markdown string, err error) {
//fmt.Println(htmlStr)
// 将字符串解析为 DOM 树
tree := lute.HTML2Tree(htmlStr)
// 将 AST 进行 Markdown 格式化渲染
var formatted []byte
renderer := render.NewFormatRenderer(tree, lute.RenderOptions)
for nodeType, rendererFunc := range lute.HTML2MdRendererFuncs {
renderer.ExtRendererFuncs[nodeType] = rendererFunc
}
formatted = renderer.Render()
markdown = util.BytesToStr(formatted)
return
}
// HTML2Tree 将 HTML 转换为 AST。
func (lute *Lute) HTML2Tree(dom string) (ret *parse.Tree) {
htmlRoot := lute.parseHTML(dom)
if nil == htmlRoot {
return
}
// 调整 DOM 结构
lute.adjustVditorDOM(htmlRoot)
// 将 HTML 树转换为 Markdown AST
ret = &parse.Tree{Name: "", Root: &ast.Node{Type: ast.NodeDocument}, Context: &parse.Context{ParseOption: lute.ParseOptions}}
ret.Context.Tip = ret.Root
for c := htmlRoot.FirstChild; nil != c; c = c.NextSibling {
lute.genASTByDOM(c, ret)
}
// 调整树结构
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if entering {
if ast.NodeList == n.Type {
// ul.ul => ul.li.ul
if nil != n.Parent && ast.NodeList == n.Parent.Type {
previousLi := n.Previous
if nil != previousLi {
n.Unlink()
previousLi.AppendChild(n)
}
}
}
}
return ast.WalkContinue
})
return
}
// genASTByDOM 根据指定的 DOM 节点 n 进行深度优先遍历并逐步生成 Markdown 语法树 tree。
func (lute *Lute) genASTByDOM(n *html.Node, tree *parse.Tree) {
if html.CommentNode == n.Type || atom.Meta == n.DataAtom {
return
}
if "svg" == n.Namespace {
return
}
dataRender := util.DomAttrValue(n, "data-render")
if "1" == dataRender {
return
}
class := util.DomAttrValue(n, "class")
if strings.HasPrefix(class, "line-number") &&
!strings.HasPrefix(class, "line-numbers" /* 简书代码块 https://github.com/siyuan-note/siyuan/issues/4361 */) {
return
}
if strings.Contains(class, "mw-editsection") {
// 忽略 Wikipedia [编辑] Do not clip the `Edit` element next to Wikipedia headings https://github.com/siyuan-note/siyuan/issues/11600
return
}
if strings.Contains(class, "citation-comment") {
// 忽略 Wikipedia 引用中的注释 https://github.com/siyuan-note/siyuan/issues/11640
return
}
if 0 == n.DataAtom && html.ElementNode == n.Type { // 自定义标签
for c := n.FirstChild; c != nil; c = c.NextSibling {
lute.genASTByDOM(c, tree)
}
return
}
node := &ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(n.Data)}
switch n.DataAtom {
case 0:
if nil != n.Parent && atom.A == n.Parent.DataAtom {
node.Type = ast.NodeLinkText
}
// 将 \n空格空格* 转换为\n
for strings.Contains(string(node.Tokens), "\n ") {
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("\n "), []byte("\n "))
}
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("\n "), []byte("\n"))
node.Tokens = bytes.Trim(node.Tokens, "\t\n")
if lute.parentIs(n, atom.Table) {
if "\n" == n.Data {
if nil == tree.Context.Tip.FirstChild || nil == n.NextSibling {
break
}
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeBr})
break
} else {
if "" == strings.TrimSpace(n.Data) {
node.Tokens = []byte(" ")
tree.Context.Tip.AppendChild(node)
break
}
}
node.Tokens = bytes.TrimSpace(node.Tokens)
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("\n"), []byte(" "))
}
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte{194, 160}, []byte{' '}) // 将   转换为空格
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("\n"), []byte{' '}) // 将 \n 转换为空格 https://github.com/siyuan-note/siyuan/issues/6052
if ast.NodeStrong == tree.Context.Tip.Type ||
ast.NodeEmphasis == tree.Context.Tip.Type ||
ast.NodeStrikethrough == tree.Context.Tip.Type ||
ast.NodeMark == tree.Context.Tip.Type ||
ast.NodeSup == tree.Context.Tip.Type ||
ast.NodeSub == tree.Context.Tip.Type {
if bytes.HasPrefix(node.Tokens, []byte(" ")) || 1 > len(bytes.TrimSpace(node.Tokens)) {
if nil != tree.Context.Tip.LastChild && tree.Context.Tip.LastChild.IsMarker() {
node.Tokens = append([]byte(editor.Zwsp), node.Tokens...)
}
}
if bytes.HasSuffix(node.Tokens, []byte(" ")) && nil == n.NextSibling {
node.Tokens = append(node.Tokens, []byte(editor.Zwsp)...)
}
}
if lute.ParseOptions.ProtyleWYSIWYG {
node.Tokens = lex.EscapeProtyleMarkers(node.Tokens)
} else {
node.Tokens = lex.EscapeCommonMarkers(node.Tokens)
if lute.parentIs(n, atom.Table) {
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("\\|"), []byte("|"))
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("|"), []byte("\\|"))
}
}
tree.Context.Tip.AppendChild(node)
case atom.P, atom.Div, atom.Section, atom.Dd:
if ast.NodeLink == tree.Context.Tip.Type {
break
}
if lute.parentIs(n, atom.Table) {
if nil != n.PrevSibling && strings.Contains(n.PrevSibling.Data, "\n") {
break
}
if nil != n.NextSibling && strings.Contains(n.NextSibling.Data, "\n") {
break
}
if nil == tree.Context.Tip.FirstChild {
break
}
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeBr})
break
}
if ast.NodeHeading == tree.Context.Tip.Type {
// h 下存在 div/p/section 则忽略分块
break
}
class := util.DomAttrValue(n, "class")
if atom.Div == n.DataAtom || atom.Section == n.DataAtom {
// 解析 GitHub 语法高亮代码块
language := ""
if strings.Contains(class, "-source-") {
language = class[strings.LastIndex(class, "-source-")+len("-source-"):]
} else if strings.Contains(class, "-text-html-basic") {
language = "html"
}
if "" != language {
node.Type = ast.NodeCodeBlock
node.IsFencedCodeBlock = true
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceOpenMarker, Tokens: util.StrToBytes("```"), CodeBlockFenceLen: 3})
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceInfoMarker})
buf := &bytes.Buffer{}
node.LastChild.CodeBlockInfo = []byte(language)
buf.WriteString(util.DomText(n))
content := &ast.Node{Type: ast.NodeCodeBlockCode, Tokens: buf.Bytes()}
node.AppendChild(content)
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceCloseMarker, Tokens: util.StrToBytes("```"), CodeBlockFenceLen: 3})
tree.Context.Tip.AppendChild(node)
return
}
// The browser extension supports CSDN formula https://github.com/siyuan-note/siyuan/issues/5624
if strings.Contains(class, "MathJax") && nil != n.NextSibling && atom.Script == n.NextSibling.DataAtom && strings.Contains(util.DomAttrValue(n.NextSibling, "type"), "math/tex") {
tex := util.DomText(n.NextSibling)
appendMathBlock(tree, tex)
n.NextSibling.Unlink()
return
}
// The browser extension supports Wikipedia formula clipping https://github.com/siyuan-note/siyuan/issues/11583
if tex := strings.TrimSpace(util.DomAttrValue(n, "data-tex")); "" != tex {
appendMathBlock(tree, tex)
return
}
}
if strings.Contains(strings.ToLower(class), "mathjax") {
return
}
if "" == strings.TrimSpace(util.DomText(n)) {
for { // 这里用 for 是为了简化实现
if util.DomExistChildByType(n, atom.Img, atom.Picture, atom.Annotation) {
break
}
// span 可能是 TextMark 元素,也可能是公式,其他情况则忽略
spans := util.DomChildrenByType(n, atom.Span)
if 0 < len(spans) {
span := spans[0]
if "" != util.DomAttrValue(span, "data-type") || "" != util.DomAttrValue(span, "data-tex") {
break
}
}
return
}
}
if tree.Context.Tip.IsBlock() && !tree.Context.Tip.IsContainerBlock() {
tree.Context.ParentTip()
}
node.Type = ast.NodeParagraph
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.H1, atom.H2, atom.H3, atom.H4, atom.H5, atom.H6:
if ast.NodeLink == tree.Context.Tip.Type {
break
}
node.Type = ast.NodeHeading
node.HeadingLevel = int(node.Tokens[1] - byte('0'))
node.AppendChild(&ast.Node{Type: ast.NodeHeadingC8hMarker, Tokens: util.StrToBytes(strings.Repeat("#", node.HeadingLevel))})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Hr:
node.Type = ast.NodeThematicBreak
tree.Context.Tip.AppendChild(node)
case atom.Blockquote:
node.Type = ast.NodeBlockquote
node.AppendChild(&ast.Node{Type: ast.NodeBlockquoteMarker, Tokens: util.StrToBytes(">")})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Ol, atom.Ul:
node.Type = ast.NodeList
node.ListData = &ast.ListData{}
if atom.Ol == n.DataAtom {
node.ListData.Typ = 1
}
node.ListData.Tight = true
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Li:
node.Type = ast.NodeListItem
marker := util.DomAttrValue(n, "data-marker")
var bullet byte
if "" == marker {
if nil != n.Parent && atom.Ol == n.Parent.DataAtom {
start := util.DomAttrValue(n.Parent, "start")
if "" == start {
marker = "1."
} else {
marker = start + "."
}
} else {
marker = "*"
bullet = marker[0]
}
} else {
if nil != n.Parent && "1." != marker && atom.Ol == n.Parent.DataAtom && nil != n.Parent.Parent && (atom.Ol == n.Parent.Parent.DataAtom || atom.Ul == n.Parent.Parent.DataAtom) {
// 子有序列表必须从 1 开始
marker = "1."
}
}
node.ListData = &ast.ListData{Marker: []byte(marker), BulletChar: bullet}
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Pre:
firstc := n.FirstChild
if nil == firstc {
return
}
if atom.Div == firstc.DataAtom && nil != firstc.NextSibling && atom.Code == firstc.NextSibling.DataAtom {
firstc = firstc.NextSibling
n.FirstChild.Unlink()
}
if atom.Div == firstc.DataAtom && nil == firstc.NextSibling {
codes := util.DomChildrenByType(n, atom.Code)
if 1 == len(codes) {
code := codes[0]
// pre 下只有一个 div,且 div 下只有一个 code,那么将 pre.div 替换为 pre.code https://github.com/siyuan-note/siyuan/issues/11131
code.Unlink()
n.AppendChild(code)
firstc.Unlink()
firstc = n.FirstChild
}
}
// 改进两种 pre.ol.li 的代码块解析 https://github.com/siyuan-note/siyuan/issues/11296
// 第一种:将 pre.ol.li.p.span, span, ... span 转换为 pre.ol.li.p.code, code, ... code,然后交由第二种处理
span2Code := false
if atom.Ol == firstc.DataAtom && nil == firstc.NextSibling && nil != firstc.FirstChild && atom.Li == firstc.FirstChild.DataAtom &&
nil != firstc.FirstChild.FirstChild && atom.P == firstc.FirstChild.FirstChild.DataAtom &&
nil != firstc.FirstChild.FirstChild.FirstChild && atom.Span == firstc.FirstChild.FirstChild.FirstChild.DataAtom {
for li := firstc.FirstChild; nil != li; li = li.NextSibling {
code := &html.Node{Data: "code", DataAtom: atom.Code, Type: html.ElementNode}
var spans []*html.Node
for span := li.FirstChild.FirstChild; nil != span; span = span.NextSibling {
spans = append(spans, span)
}
for _, span := range spans {
span.Unlink()
code.AppendChild(span)
}
li.FirstChild.AppendChild(code)
span2Code = true
}
}
// 第二种:将 pre.ol.li.p.code, code, ... code 转换为 pre.code, code, ... code,然后交由后续处理
if atom.Ol == firstc.DataAtom && nil == firstc.NextSibling && nil != firstc.FirstChild && atom.Li == firstc.FirstChild.DataAtom &&
nil != firstc.FirstChild.FirstChild && atom.P == firstc.FirstChild.FirstChild.DataAtom &&
nil != firstc.FirstChild.FirstChild.FirstChild && atom.Code == firstc.FirstChild.FirstChild.FirstChild.DataAtom {
var lis, codes []*html.Node
for li := firstc.FirstChild; nil != li; li = li.NextSibling {
lis = append(lis, li)
codes = append(codes, li.FirstChild.FirstChild)
}
for _, li := range lis {
li.Unlink()
}
for _, code := range codes {
code.Unlink()
n.AppendChild(code)
}
firstc.Unlink()
firstc = n.FirstChild
}
if html.TextNode == firstc.Type || atom.Span == firstc.DataAtom || atom.Code == firstc.DataAtom || atom.Section == firstc.DataAtom || atom.Pre == firstc.DataAtom || atom.A == firstc.DataAtom {
node.Type = ast.NodeCodeBlock
node.IsFencedCodeBlock = true
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceOpenMarker, Tokens: util.StrToBytes("```"), CodeBlockFenceLen: 3})
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceInfoMarker})
if atom.Code == firstc.DataAtom || atom.Span == firstc.DataAtom || atom.A == firstc.DataAtom {
class := util.DomAttrValue(firstc, "class")
if !strings.Contains(class, "language-") {
class = util.DomAttrValue(n, "class")
}
if strings.Contains(class, "language-") {
language := class[strings.Index(class, "language-")+len("language-"):]
language = strings.Split(language, " ")[0]
node.LastChild.CodeBlockInfo = []byte(language)
} else {
if atom.Code == firstc.DataAtom && !span2Code {
class := util.DomAttrValue(firstc, "class")
if !strings.Contains(class, " ") {
node.LastChild.CodeBlockInfo = []byte(class)
}
}
}
if 1 > len(node.LastChild.CodeBlockInfo) {
class := util.DomAttrValue(n, "class")
if !strings.Contains(class, " ") {
node.LastChild.CodeBlockInfo = []byte(class)
}
}
if 1 > len(node.LastChild.CodeBlockInfo) {
lang := util.DomAttrValue(n, "data-language")
if !strings.Contains(lang, " ") {
node.LastChild.CodeBlockInfo = []byte(lang)
}
}
if bytes.ContainsAny(node.LastChild.CodeBlockInfo, "-_ ") {
node.LastChild.CodeBlockInfo = nil
}
}
if atom.Code == firstc.DataAtom {
if nil != firstc.NextSibling && atom.Code == firstc.NextSibling.DataAtom {
// pre.code code 每个 code 为一行的结构,需要在 code 中间插入换行
for c := firstc.NextSibling; nil != c; c = c.NextSibling {
c.InsertBefore(&html.Node{DataAtom: atom.Br})
}
}
if nil != firstc.FirstChild && atom.Ol == firstc.FirstChild.DataAtom {
// CSDN 代码块:pre.code.ol.li
for li := firstc.FirstChild.FirstChild; nil != li; li = li.NextSibling {
if li != firstc.FirstChild.FirstChild {
li.InsertBefore(&html.Node{DataAtom: atom.Br})
}
}
}
if nil != n.LastChild && atom.Ul == n.LastChild.DataAtom {
// CSDN 代码块:pre.code,ul
n.LastChild.Unlink() // 去掉最后一个代码行号子块 https://github.com/siyuan-note/siyuan/issues/5564
}
}
if atom.Pre == firstc.DataAtom && nil != firstc.FirstChild {
// pre.code code 每个 code 为一行的结构,需要在 code 中间插入换行
for c := firstc.FirstChild.NextSibling; nil != c; c = c.NextSibling {
c.InsertBefore(&html.Node{DataAtom: atom.Br})
}
}
buf := &bytes.Buffer{}
buf.WriteString(util.DomText(n))
tokens := buf.Bytes()
tokens = bytes.ReplaceAll(tokens, []byte("\u00A0"), []byte(" "))
content := &ast.Node{Type: ast.NodeCodeBlockCode, Tokens: tokens}
node.AppendChild(content)
node.AppendChild(&ast.Node{Type: ast.NodeCodeBlockFenceCloseMarker, Tokens: util.StrToBytes("```"), CodeBlockFenceLen: 3})
if tree.Context.Tip.ParentIs(ast.NodeTable) {
// 如果表格中只有一行一列,那么丢弃表格直接使用代码块
// Improve HTML parsing code blocks https://github.com/siyuan-note/siyuan/issues/11068
for table := tree.Context.Tip.Parent; nil != table; table = table.Parent {
if ast.NodeTable == table.Type {
if nil != table.FirstChild && table.FirstChild == table.LastChild && ast.NodeTableHead == table.FirstChild.Type &&
table.FirstChild.FirstChild == table.FirstChild.LastChild &&
nil != table.FirstChild.FirstChild.FirstChild && ast.NodeTableCell == table.FirstChild.FirstChild.FirstChild.Type {
table.InsertBefore(node)
table.Unlink()
tree.Context.Tip = node
return
}
}
}
// 表格中不支持添加块级元素,所以这里只能将其转换为多个行级代码元素
lines := bytes.Split(content.Tokens, []byte("\n"))
for i, line := range lines {
if 0 < len(line) {
code := &ast.Node{Type: ast.NodeCodeSpan}
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanOpenMarker, Tokens: []byte("`")})
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanContent, Tokens: line})
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanCloseMarker, Tokens: []byte("`")})
tree.Context.Tip.AppendChild(code)
if i < len(lines)-1 {
if tree.Context.ParseOption.ProtyleWYSIWYG {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeBr})
} else {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeHardBreak, Tokens: []byte("\n")})
}
}
}
}
} else {
tree.Context.Tip.AppendChild(node)
}
} else {
node.Type = ast.NodeHTMLBlock
node.Tokens = util.DomHTML(n)
tree.Context.Tip.AppendChild(node)
}
return
case atom.Em, atom.I:
text := util.DomText(n)
if "" == strings.TrimSpace(text) {
break
}
if ast.NodeEmphasis == tree.Context.Tip.Type || tree.Context.Tip.ParentIs(ast.NodeEmphasis) {
break
}
if nil != tree.Context.Tip.LastChild && (ast.NodeStrong == tree.Context.Tip.LastChild.Type || ast.NodeEmphasis == tree.Context.Tip.LastChild.Type) {
// 在两个相邻的加粗或者斜体之间插入零宽空格,避免标记符重复
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(editor.Zwsp)})
}
node.Type = ast.NodeEmphasis
marker := "*"
node.AppendChild(&ast.Node{Type: ast.NodeEmA6kOpenMarker, Tokens: util.StrToBytes(marker)})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Strong, atom.B:
text := util.DomText(n)
if "" == strings.TrimSpace(text) {
break
}
if ast.NodeStrong == tree.Context.Tip.Type || tree.Context.Tip.ParentIs(ast.NodeStrong) {
break
}
if nil != tree.Context.Tip.LastChild && (ast.NodeStrong == tree.Context.Tip.LastChild.Type || ast.NodeEmphasis == tree.Context.Tip.LastChild.Type) {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(editor.Zwsp)})
}
node.Type = ast.NodeStrong
marker := "**"
node.AppendChild(&ast.Node{Type: ast.NodeStrongA6kOpenMarker, Tokens: util.StrToBytes(marker)})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Code:
if nil == n.FirstChild {
return
}
if nil != tree.Context.Tip.LastChild && ast.NodeCodeSpan == tree.Context.Tip.LastChild.Type {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(editor.Zwsp)})
}
code := util.DomHTML(n)
if bytes.Contains(code, []byte(">")) {
code = code[bytes.Index(code, []byte(">"))+1:]
}
code = bytes.TrimSuffix(code, []byte("</code>"))
allSpan := true
for c := n.FirstChild; nil != c; c = c.NextSibling {
if html.TextNode == c.Type {
continue
}
if atom.Em == c.DataAtom || atom.Strong == c.DataAtom {
// https://github.com/siyuan-note/siyuan/issues/11682
continue
}
if atom.Span != c.DataAtom {
allSpan = false
break
}
}
if allSpan {
// 如果全部都是 span 子节点,那么直接使用 span 的内容 https://github.com/siyuan-note/siyuan/issues/11281
code = []byte(util.DomText(n))
code = bytes.ReplaceAll(code, []byte("\u00A0"), []byte(" "))
}
content := &ast.Node{Type: ast.NodeCodeSpanContent, Tokens: code}
node.Type = ast.NodeCodeSpan
node.AppendChild(&ast.Node{Type: ast.NodeCodeSpanOpenMarker, Tokens: []byte("`")})
node.AppendChild(content)
node.AppendChild(&ast.Node{Type: ast.NodeCodeSpanCloseMarker, Tokens: []byte("`")})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
return
case atom.Br:
if ast.NodeLink == tree.Context.Tip.Type {
break
}
if nil == n.NextSibling {
break
}
if tree.Context.ParseOption.ProtyleWYSIWYG && lute.parentIs(n, atom.Table) {
node.Type = ast.NodeBr
} else {
node.Type = ast.NodeHardBreak
node.Tokens = util.StrToBytes("\n")
}
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.A:
node.Type = ast.NodeLink
text := strings.TrimSpace(util.DomText(n))
if "" == text && nil != n.Parent && lute.parentIs(n, atom.H1, atom.H2, atom.H3, atom.H4, atom.H5, atom.H6, atom.Div, atom.Section) && nil == util.DomChildrenByType(n, atom.Img) {
// 丢弃标题中文本为空的链接,这样的链接是没有锚文本的锚点
// https://github.com/Vanessa219/vditor/issues/359
// https://github.com/siyuan-note/siyuan/issues/11445
return
}
if "" == text && nil == n.FirstChild {
// 剪藏时过滤空的超链接 https://github.com/siyuan-note/siyuan/issues/5686
return
}
if nil != n.FirstChild && atom.Img == n.FirstChild.DataAtom && strings.Contains(util.DomAttrValue(n.FirstChild, "src"), "wikimedia.org") {
// Wikipedia 链接嵌套图片的情况只保留图片
break
}
node.AppendChild(&ast.Node{Type: ast.NodeOpenBracket})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Img:
imgClass := util.DomAttrValue(n, "class")
imgAlt := util.DomAttrValue(n, "alt")
if "emoji" == imgClass {
node.Type = ast.NodeEmoji
emojiImg := &ast.Node{Type: ast.NodeEmojiImg, Tokens: tree.EmojiImgTokens(imgAlt, util.DomAttrValue(n, "src"))}
emojiImg.AppendChild(&ast.Node{Type: ast.NodeEmojiAlias, Tokens: util.StrToBytes(":" + imgAlt + ":")})
node.AppendChild(emojiImg)
} else {
node.Type = ast.NodeImage
node.AppendChild(&ast.Node{Type: ast.NodeBang})
node.AppendChild(&ast.Node{Type: ast.NodeOpenBracket})
if "" != imgAlt {
node.AppendChild(&ast.Node{Type: ast.NodeLinkText, Tokens: util.StrToBytes(imgAlt)})
}
node.AppendChild(&ast.Node{Type: ast.NodeCloseBracket})
node.AppendChild(&ast.Node{Type: ast.NodeOpenParen})
src := util.DomAttrValue(n, "src")
if strings.HasPrefix(src, "data:image") {
// 处理可能存在的预加载情况
if dataSrc := util.DomAttrValue(n, "data-src"); "" != dataSrc {
src = dataSrc
}
}
// 处理使用 data-original 属性的情况 https://github.com/siyuan-note/siyuan/issues/11826
dataOriginal := util.DomAttrValue(n, "data-original")
if "" != dataOriginal {
if "" == src || !strings.HasSuffix(src, ".gif") {
src = dataOriginal
}
}
if "" == src {
// 处理使用 srcset 属性的情况
if srcset := util.DomAttrValue(n, "srcset"); "" != srcset {
if strings.Contains(srcset, ",") {
src = strings.Split(srcset, ",")[len(strings.Split(srcset, ","))-1]
src = strings.TrimSpace(src)
if strings.Contains(src, " ") {
src = strings.TrimSpace(strings.Split(src, " ")[0])
}
} else {
src = strings.TrimSpace(src)
if strings.Contains(src, " ") {
src = strings.TrimSpace(strings.Split(srcset, " ")[0])
}
}
}
}
// Wikipedia 使用图片原图 https://github.com/siyuan-note/siyuan/issues/11640
if strings.Contains(src, "wikipedia/commons/thumb/") {
ext := path.Ext(src)
if strings.Contains(src, ".svg.png") {
ext = ".svg"
}
if idx := strings.Index(src, ext+"/"); 0 < idx {
src = src[:idx+len(ext)]
src = strings.ReplaceAll(src, "/commons/thumb/", "/commons/")
}
}
node.AppendChild(&ast.Node{Type: ast.NodeLinkDest, Tokens: util.StrToBytes(src)})
var linkTitle string
if nil != n.NextSibling && atom.Figcaption == n.NextSibling.DataAtom {
linkTitle = util.DomText(n.NextSibling)
n.NextSibling.Unlink()
}
if "" == linkTitle {
linkTitle = util.DomAttrValue(n, "title")
}
if "" != linkTitle {
node.AppendChild(&ast.Node{Type: ast.NodeLinkSpace})
node.AppendChild(&ast.Node{Type: ast.NodeLinkTitle, Tokens: []byte(linkTitle)})
}
node.AppendChild(&ast.Node{Type: ast.NodeCloseParen})
}
if ast.NodeDocument == tree.Context.Tip.Type {
p := &ast.Node{Type: ast.NodeParagraph}
tree.Context.Tip.AppendChild(p)
tree.Context.Tip = p
defer tree.Context.ParentTip()
}
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Input:
node.Type = ast.NodeTaskListItemMarker
node.TaskListItemChecked = lute.hasAttr(n, "checked")
tree.Context.Tip.AppendChild(node)
if nil != node.Parent.Parent {
if nil == node.Parent.Parent.ListData {
node.Parent.Parent.ListData = &ast.ListData{Typ: 3}
} else {
node.Parent.Parent.ListData.Typ = 3
}
}
case atom.Del, atom.S, atom.Strike:
node.Type = ast.NodeStrikethrough
marker := "~~"
node.AppendChild(&ast.Node{Type: ast.NodeStrikethrough2OpenMarker, Tokens: util.StrToBytes(marker)})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Mark:
node.Type = ast.NodeMark
marker := "=="
node.AppendChild(&ast.Node{Type: ast.NodeMark2OpenMarker, Tokens: util.StrToBytes(marker)})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Sup:
node.Type = ast.NodeSup
node.AppendChild(&ast.Node{Type: ast.NodeSupOpenMarker})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Sub:
node.Type = ast.NodeSub
node.AppendChild(&ast.Node{Type: ast.NodeSubOpenMarker})
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Table:
node.Type = ast.NodeTable
var tableAligns []int
if nil != n.FirstChild && nil != n.FirstChild.FirstChild && nil != n.FirstChild.FirstChild.FirstChild {
for th := n.FirstChild.FirstChild.FirstChild; nil != th; th = th.NextSibling {
align := util.DomAttrValue(th, "align")
switch align {
case "left":
tableAligns = append(tableAligns, 1)
case "center":
tableAligns = append(tableAligns, 2)
case "right":
tableAligns = append(tableAligns, 3)
default:
tableAligns = append(tableAligns, 0)
}
}
}
node.TableAligns = tableAligns
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Thead:
if nil == n.FirstChild {
break
}
tbodys := util.DomChildrenByType(n.Parent, atom.Tbody)
if 0 < len(tbodys) {
tbody := tbodys[0]
// 找到最多的 td 数
var tdCount int
for tr := tbody.FirstChild; nil != tr; tr = tr.NextSibling {
if atom.Tr != tr.DataAtom {
continue
}
var count int
for td := tr.FirstChild; nil != td; td = td.NextSibling {
if atom.Td == td.DataAtom {
count++
}
}
if count > tdCount {
tdCount = count
}
}
// 补全 thead 中 tr 的 th
for tr := n.FirstChild; nil != tr; tr = tr.NextSibling {
if atom.Tr != tr.DataAtom {
continue
}
var count int
for td := tr.FirstChild; nil != td; td = td.NextSibling {
if atom.Th == td.DataAtom {
count++
}
}
if count < tdCount {
for i := count; i < tdCount; i++ {
th := &html.Node{Data: "th", DataAtom: atom.Th, Type: html.ElementNode}
tr.AppendChild(th)
}
}
}
}
node.Type = ast.NodeTableHead
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Tbody:
case atom.Tr:
if nil == n.FirstChild {
break
}
table := n.Parent.Parent
node.Type = ast.NodeTableRow
if nil == tree.Context.Tip.ChildByType(ast.NodeTableHead) && 1 > len(util.DomChildrenByType(table, atom.Thead)) {
// 补全 thread 节点
thead := &ast.Node{Type: ast.NodeTableHead}
tree.Context.Tip.AppendChild(thead)
tree.Context.Tip = thead
defer tree.Context.ParentTip()
}
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Th, atom.Td:
node.Type = ast.NodeTableCell
align := util.DomAttrValue(n, "align")
var tableAlign int
switch align {
case "left":
tableAlign = 1
case "center":
tableAlign = 2
case "right":
tableAlign = 3
default:
tableAlign = 0
}
node.TableCellAlign = tableAlign
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
case atom.Colgroup, atom.Col:
return
case atom.Span:
// Improve inline elements pasting https://github.com/siyuan-note/siyuan/issues/11740
dataType := util.DomAttrValue(n, "data-type")
dataType = strings.Split(dataType, " ")[0] // 简化为只处理第一个类型
switch dataType {
case "inline-math":
mathContent := util.DomAttrValue(n, "data-content")
appendInlineMath(tree, mathContent)
return
case "code":
if nil != tree.Context.Tip.LastChild && ast.NodeCodeSpan == tree.Context.Tip.LastChild.Type {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(editor.Zwsp)})
}
code := &ast.Node{Type: ast.NodeCodeSpan}
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanOpenMarker, Tokens: []byte("`")})
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanContent, Tokens: util.StrToBytes(util.DomText(n))})
code.AppendChild(&ast.Node{Type: ast.NodeCodeSpanCloseMarker, Tokens: []byte("`")})
tree.Context.Tip.AppendChild(code)
return
case "tag":
tag := &ast.Node{Type: ast.NodeTag}
tag.AppendChild(&ast.Node{Type: ast.NodeTagOpenMarker, Tokens: util.StrToBytes("#")})
tag.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
tag.AppendChild(&ast.Node{Type: ast.NodeTagCloseMarker, Tokens: util.StrToBytes("#")})
tree.Context.Tip.AppendChild(tag)
return
case "kbd":
if nil != tree.Context.Tip.LastChild && ast.NodeKbd == tree.Context.Tip.LastChild.Type {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(editor.Zwsp)})
}
kbd := &ast.Node{Type: ast.NodeKbd}
kbd.AppendChild(&ast.Node{Type: ast.NodeKbdOpenMarker})
kbd.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
kbd.AppendChild(&ast.Node{Type: ast.NodeKbdCloseMarker})
tree.Context.Tip.AppendChild(kbd)
return
case "sub":
sub := &ast.Node{Type: ast.NodeSub}
sub.AppendChild(&ast.Node{Type: ast.NodeSubOpenMarker})
sub.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
sub.AppendChild(&ast.Node{Type: ast.NodeSubCloseMarker})
tree.Context.Tip.AppendChild(sub)
return
case "sup":
sup := &ast.Node{Type: ast.NodeSup}
sup.AppendChild(&ast.Node{Type: ast.NodeSupOpenMarker})
sup.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
sup.AppendChild(&ast.Node{Type: ast.NodeSupCloseMarker})
tree.Context.Tip.AppendChild(sup)
return
case "mark":
mark := &ast.Node{Type: ast.NodeMark}
mark.AppendChild(&ast.Node{Type: ast.NodeMark2OpenMarker, Tokens: util.StrToBytes("==")})
mark.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
mark.AppendChild(&ast.Node{Type: ast.NodeMark2CloseMarker, Tokens: util.StrToBytes("==")})
tree.Context.Tip.AppendChild(mark)
return
case "s":
s := &ast.Node{Type: ast.NodeStrikethrough}
s.AppendChild(&ast.Node{Type: ast.NodeStrikethrough2OpenMarker, Tokens: util.StrToBytes("~~")})
s.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
s.AppendChild(&ast.Node{Type: ast.NodeStrikethrough2CloseMarker, Tokens: util.StrToBytes("~~")})
tree.Context.Tip.AppendChild(s)
return
case "u":
u := &ast.Node{Type: ast.NodeUnderline}
u.AppendChild(&ast.Node{Type: ast.NodeUnderlineOpenMarker})
u.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
u.AppendChild(&ast.Node{Type: ast.NodeUnderlineCloseMarker})
tree.Context.Tip.AppendChild(u)
return
case "em":
em := &ast.Node{Type: ast.NodeEmphasis}
em.AppendChild(&ast.Node{Type: ast.NodeEmA6kOpenMarker, Tokens: util.StrToBytes("*")})
em.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
em.AppendChild(&ast.Node{Type: ast.NodeEmA6kCloseMarker, Tokens: util.StrToBytes("*")})
tree.Context.Tip.AppendChild(em)
return
case "strong":
strong := &ast.Node{Type: ast.NodeStrong}
strong.AppendChild(&ast.Node{Type: ast.NodeStrongA6kOpenMarker, Tokens: util.StrToBytes("**")})
strong.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(util.DomText(n))})
strong.AppendChild(&ast.Node{Type: ast.NodeStrongA6kCloseMarker, Tokens: util.StrToBytes("**")})
tree.Context.Tip.AppendChild(strong)
return
case "block-ref":
ref := &ast.Node{Type: ast.NodeBlockRef}
ref.AppendChild(&ast.Node{Type: ast.NodeOpenParen})
ref.AppendChild(&ast.Node{Type: ast.NodeOpenParen})
ref.AppendChild(&ast.Node{Type: ast.NodeBlockRefID, Tokens: util.StrToBytes(util.DomAttrValue(n, "data-id"))})
ref.AppendChild(&ast.Node{Type: ast.NodeBlockRefSpace})
if "s" == util.DomAttrValue(n, "data-subtype") {
ref.AppendChild(&ast.Node{Type: ast.NodeBlockRefText, Tokens: util.StrToBytes(util.DomText(n))})
} else {
ref.AppendChild(&ast.Node{Type: ast.NodeBlockRefDynamicText, Tokens: util.StrToBytes(util.DomText(n))})
}
ref.AppendChild(&ast.Node{Type: ast.NodeCloseParen})
ref.AppendChild(&ast.Node{Type: ast.NodeCloseParen})
tree.Context.Tip.AppendChild(ref)
return
}
// The browser extension supports Zhihu formula https://github.com/siyuan-note/siyuan/issues/5599
if tex := strings.TrimSpace(util.DomAttrValue(n, "data-tex")); "" != tex {
if strings.HasSuffix(strings.TrimSpace(tex), "\\\\") || strings.Contains(tex, "\n") {
appendMathBlock(tree, tex)
} else {
appendInlineMath(tree, tex)
}
return
}
// The browser extension supports CSDN formula https://github.com/siyuan-note/siyuan/issues/5624
if strings.Contains(strings.ToLower(strings.TrimSpace(util.DomAttrValue(n, "class"))), "katex") {
if span := util.DomChildByTypeAndClass(n, atom.Span, "katex-mathml"); nil != span {
if tex := util.DomText(span.FirstChild); "" != tex {
tex = strings.TrimSpace(tex)
for strings.Contains(tex, "\n ") {
tex = strings.ReplaceAll(tex, "\n ", "\n")
}
// 根据最后 4 个换行符分隔公式内容
if idx := strings.LastIndex(tex, "\n\n\n\n"); 0 < idx {
tex = tex[idx+4:]
tex = strings.TrimSpace(tex)
appendInlineMath(tree, tex)
return
}
}
}
}
if strings.Contains(strings.ToLower(strings.TrimSpace(util.DomAttrValue(n, "class"))), "mathjax") {
scripts := util.DomChildrenByType(n, atom.Script)
if 0 < len(scripts) {
script := scripts[0]
if tex := util.DomText(script.FirstChild); "" != tex {
appendInlineMath(tree, tex)
return
}
}
return
}
case atom.Font:
node.Type = ast.NodeText
tokens := []byte(util.DomText(n))
for strings.Contains(string(tokens), "\n\n") {
tokens = bytes.ReplaceAll(tokens, []byte("\n\n"), []byte("\n"))
}
for strings.Contains(string(tokens), "\n ") {
tokens = bytes.ReplaceAll(tokens, []byte("\n "), []byte("\n "))
}
tokens = bytes.ReplaceAll(tokens, []byte("\n "), []byte("\n"))
tokens = bytes.ReplaceAll(tokens, []byte("\n"), []byte(" "))
node.Tokens = tokens
tree.Context.Tip.AppendChild(node)
return
case atom.Details:
node.Type = ast.NodeHTMLBlock
node.Tokens = util.DomHTML(n)
node.Tokens = bytes.SplitAfter(node.Tokens, []byte("</summary>"))[0]
tree.Context.Tip.AppendChild(node)
case atom.Summary:
return
case atom.Iframe, atom.Audio, atom.Video:
node.Type = ast.NodeHTMLBlock
node.Tokens = util.DomHTML(n)
tree.Context.Tip.AppendChild(node)
return
case atom.Noscript:
return
case atom.Script:
if tex := util.DomText(n.FirstChild); "" != tex {
if tree.Context.Tip.IsContainerBlock() {
appendMathBlock(tree, tex)
} else {
appendInlineMath(tree, tex)
}
return
}
case atom.Figcaption:
if tree.Context.Tip.IsBlock() {
if ast.NodeDocument != tree.Context.Tip.Type {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeHardBreak})
break
}
}
node.Type = ast.NodeParagraph
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
case atom.Figure:
node.Type = ast.NodeParagraph
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
default:
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
lute.genASTByDOM(c, tree)
}
switch n.DataAtom {
case atom.Em, atom.I:
marker := "*"
node.AppendChild(&ast.Node{Type: ast.NodeEmA6kCloseMarker, Tokens: util.StrToBytes(marker)})
appendSpace(n, tree, lute)
case atom.Strong, atom.B:
marker := "**"
node.AppendChild(&ast.Node{Type: ast.NodeStrongA6kCloseMarker, Tokens: util.StrToBytes(marker)})
appendSpace(n, tree, lute)
case atom.A:
node.AppendChild(&ast.Node{Type: ast.NodeCloseBracket})
node.AppendChild(&ast.Node{Type: ast.NodeOpenParen})
node.AppendChild(&ast.Node{Type: ast.NodeLinkDest, Tokens: util.StrToBytes(util.DomAttrValue(n, "href"))})
linkTitle := util.DomAttrValue(n, "title")
if "" != linkTitle {
node.AppendChild(&ast.Node{Type: ast.NodeLinkSpace})
node.AppendChild(&ast.Node{Type: ast.NodeLinkTitle, Tokens: util.StrToBytes(linkTitle)})
}
node.AppendChild(&ast.Node{Type: ast.NodeCloseParen})
case atom.Del, atom.S, atom.Strike:
marker := "~~"
node.AppendChild(&ast.Node{Type: ast.NodeStrikethrough2CloseMarker, Tokens: util.StrToBytes(marker)})
appendSpace(n, tree, lute)
case atom.Mark:
marker := "=="
node.AppendChild(&ast.Node{Type: ast.NodeMark2CloseMarker, Tokens: util.StrToBytes(marker)})
appendSpace(n, tree, lute)
case atom.Sup:
node.AppendChild(&ast.Node{Type: ast.NodeSupCloseMarker})
appendSpace(n, tree, lute)
case atom.Sub:
node.AppendChild(&ast.Node{Type: ast.NodeSubCloseMarker})
appendSpace(n, tree, lute)
case atom.Details:
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeHTMLBlock, Tokens: []byte("</details>")})
}
}
func appendInlineMath(tree *parse.Tree, tex string) {
tex = strings.TrimSpace(tex)
if "" == tex {
return
}
inlineMath := &ast.Node{Type: ast.NodeInlineMath}
inlineMath.AppendChild(&ast.Node{Type: ast.NodeInlineMathOpenMarker, Tokens: []byte("$")})
inlineMath.AppendChild(&ast.Node{Type: ast.NodeInlineMathContent, Tokens: util.StrToBytes(tex)})
inlineMath.AppendChild(&ast.Node{Type: ast.NodeInlineMathCloseMarker, Tokens: []byte("$")})
tree.Context.Tip.AppendChild(inlineMath)
tree.Context.Tip = inlineMath
defer tree.Context.ParentTip()
}
func appendMathBlock(tree *parse.Tree, tex string) {
tex = strings.TrimSpace(tex)
if "" == tex {
return
}
mathBlock := &ast.Node{Type: ast.NodeMathBlock}
mathBlock.AppendChild(&ast.Node{Type: ast.NodeMathBlockOpenMarker, Tokens: []byte("$$")})
mathBlock.AppendChild(&ast.Node{Type: ast.NodeMathBlockContent, Tokens: util.StrToBytes(tex)})
mathBlock.AppendChild(&ast.Node{Type: ast.NodeMathBlockCloseMarker, Tokens: []byte("$$")})
tree.Context.Tip.AppendChild(mathBlock)
tree.Context.Tip = mathBlock
defer tree.Context.ParentTip()
}
func appendSpace(n *html.Node, tree *parse.Tree, lute *Lute) {
if nil != n.NextSibling {
if nextText := util.DomText(n.NextSibling); "" != nextText {
if runes := []rune(nextText); !unicode.IsSpace(runes[0]) {
if unicode.IsPunct(runes[0]) || unicode.IsSymbol(runes[0]) {
tree.Context.Tip.InsertBefore(&ast.Node{Type: ast.NodeText, Tokens: []byte(editor.Zwsp)})
tree.Context.Tip.InsertAfter(&ast.Node{Type: ast.NodeText, Tokens: []byte(editor.Zwsp)})
return
}
if curText := util.DomText(n); "" != curText {
runes = []rune(curText)
if lastC := runes[len(runes)-1]; unicode.IsPunct(lastC) || unicode.IsSymbol(lastC) {
text := tree.Context.Tip.ChildByType(ast.NodeText)
if nil != text {
text.Tokens = append([]byte(editor.Zwsp), text.Tokens...)
text.Tokens = append(text.Tokens, []byte(editor.Zwsp)...)
}
return
}
spaces := lute.prefixSpaces(curText)
if "" != spaces {
previous := tree.Context.Tip.Previous
if nil != previous {
if ast.NodeText == previous.Type {
previous.Tokens = append(previous.Tokens, util.StrToBytes(spaces)...)
} else {
previous.InsertAfter(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(spaces)})
}
} else {
tree.Context.Tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(spaces)})
}
text := tree.Context.Tip.ChildByType(ast.NodeText)
text.Tokens = bytes.TrimLeft(text.Tokens, " \u0160")
}
spaces = lute.suffixSpaces(curText)
if "" != spaces {
texts := tree.Context.Tip.ChildrenByType(ast.NodeText)
if 0 < len(texts) {
text := texts[len(texts)-1]
text.Tokens = bytes.TrimRight(text.Tokens, " \u0160")
if 1 > len(text.Tokens) {
text.Unlink()
}
}
if nil != n.NextSibling {
if html.TextNode == n.NextSibling.Type {
n.NextSibling.Data = spaces + n.NextSibling.Data
} else {
tree.Context.Tip.InsertAfter(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(spaces)})
}
} else {
tree.Context.Tip.InsertAfter(&ast.Node{Type: ast.NodeText, Tokens: util.StrToBytes(spaces)})
}
}
}
}
}
}
}
1
https://api.gitlife.ru/oschina-mirror/dl88250-lute.git
git@api.gitlife.ru:oschina-mirror/dl88250-lute.git
oschina-mirror
dl88250-lute
dl88250-lute
master