node.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // SiYuan - Build Your Eternal Digital Garden
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package treenode
  17. import (
  18. "bytes"
  19. "strings"
  20. "github.com/88250/lute"
  21. "github.com/88250/lute/ast"
  22. "github.com/88250/lute/editor"
  23. "github.com/88250/lute/html"
  24. "github.com/88250/lute/lex"
  25. "github.com/88250/lute/parse"
  26. "github.com/88250/lute/render"
  27. "github.com/88250/lute/util"
  28. "github.com/siyuan-note/logging"
  29. )
  30. func NodeStaticMdContent(node *ast.Node, luteEngine *lute.Lute) (md, content string) {
  31. md = FormatNode(node, luteEngine)
  32. content = NodeStaticContent(node)
  33. return
  34. }
  35. func FormatNode(node *ast.Node, luteEngine *lute.Lute) string {
  36. markdown, err := lute.FormatNodeSync(node, luteEngine.ParseOptions, luteEngine.RenderOptions)
  37. if nil != err {
  38. root := TreeRoot(node)
  39. logging.LogFatalf("format node [%s] in tree [%s] failed: %s", node.ID, root.ID, err)
  40. }
  41. return markdown
  42. }
  43. func NodeStaticContent(node *ast.Node) string {
  44. if nil == node {
  45. return ""
  46. }
  47. if ast.NodeDocument == node.Type {
  48. return node.IALAttr("title")
  49. }
  50. buf := bytes.Buffer{}
  51. buf.Grow(4096)
  52. lastSpace := false
  53. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  54. if !entering {
  55. return ast.WalkContinue
  56. }
  57. if n.IsContainerBlock() {
  58. if !lastSpace {
  59. buf.WriteString(" ")
  60. lastSpace = true
  61. }
  62. return ast.WalkContinue
  63. }
  64. switch n.Type {
  65. case ast.NodeTagOpenMarker, ast.NodeTagCloseMarker:
  66. buf.WriteByte('#')
  67. case ast.NodeBlockRef:
  68. buf.WriteString(GetDynamicBlockRefText(n))
  69. lastSpace = false
  70. return ast.WalkSkipChildren
  71. case ast.NodeLinkText:
  72. buf.Write(n.Tokens)
  73. buf.WriteByte(' ')
  74. case ast.NodeLinkDest:
  75. buf.Write(n.Tokens)
  76. buf.WriteByte(' ')
  77. case ast.NodeLinkTitle:
  78. buf.Write(n.Tokens)
  79. case ast.NodeText, ast.NodeFileAnnotationRefText, ast.NodeFootnotesRef,
  80. ast.NodeCodeSpanContent, ast.NodeInlineMathContent, ast.NodeCodeBlockCode, ast.NodeMathBlockContent, ast.NodeHTMLBlock:
  81. buf.Write(n.Tokens)
  82. case ast.NodeBackslash:
  83. buf.WriteByte(lex.ItemBackslash)
  84. case ast.NodeBackslashContent:
  85. buf.Write(n.Tokens)
  86. }
  87. lastSpace = false
  88. return ast.WalkContinue
  89. })
  90. return strings.TrimSpace(buf.String())
  91. }
  92. func FirstLeafBlock(node *ast.Node) (ret *ast.Node) {
  93. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  94. if !entering || n.IsMarker() {
  95. return ast.WalkContinue
  96. }
  97. if !n.IsContainerBlock() {
  98. ret = n
  99. return ast.WalkStop
  100. }
  101. return ast.WalkContinue
  102. })
  103. return
  104. }
  105. func CountBlockNodes(node *ast.Node) (ret int) {
  106. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  107. if !entering || !n.IsBlock() || ast.NodeList == n.Type || ast.NodeBlockquote == n.Type || ast.NodeSuperBlock == n.Type {
  108. return ast.WalkContinue
  109. }
  110. if "1" == n.IALAttr("fold") {
  111. ret++
  112. return ast.WalkSkipChildren
  113. }
  114. ret++
  115. return ast.WalkContinue
  116. })
  117. return
  118. }
  119. func ParentNodes(node *ast.Node) (parents []*ast.Node) {
  120. for n := node.Parent; nil != n; n = n.Parent {
  121. parents = append(parents, n)
  122. if ast.NodeDocument == n.Type {
  123. return
  124. }
  125. }
  126. return
  127. }
  128. func ParentBlock(node *ast.Node) *ast.Node {
  129. for p := node.Parent; nil != p; p = p.Parent {
  130. if "" != p.ID && p.IsBlock() {
  131. return p
  132. }
  133. }
  134. return nil
  135. }
  136. func GetNodeInTree(tree *parse.Tree, id string) (ret *ast.Node) {
  137. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  138. if !entering {
  139. return ast.WalkContinue
  140. }
  141. if id == n.ID {
  142. ret = n
  143. ret.Box = tree.Box
  144. ret.Path = tree.Path
  145. return ast.WalkStop
  146. }
  147. return ast.WalkContinue
  148. })
  149. return
  150. }
  151. func GetDocTitleImgPath(root *ast.Node) (ret string) {
  152. if nil == root {
  153. return
  154. }
  155. const background = "background-image: url("
  156. titleImg := root.IALAttr("title-img")
  157. titleImg = strings.TrimSpace(titleImg)
  158. titleImg = html.UnescapeString(titleImg)
  159. titleImg = strings.ReplaceAll(titleImg, "background-image:url(", background)
  160. if !strings.Contains(titleImg, background) {
  161. return
  162. }
  163. start := strings.Index(titleImg, background) + len(background)
  164. end := strings.LastIndex(titleImg, ")")
  165. ret = titleImg[start:end]
  166. ret = strings.TrimPrefix(ret, "\"")
  167. ret = strings.TrimPrefix(ret, "'")
  168. ret = strings.TrimSuffix(ret, "\"")
  169. ret = strings.TrimSuffix(ret, "'")
  170. return ret
  171. }
  172. var typeAbbrMap = map[string]string{
  173. // 块级元素
  174. "NodeDocument": "d",
  175. "NodeHeading": "h",
  176. "NodeList": "l",
  177. "NodeListItem": "i",
  178. "NodeCodeBlock": "c",
  179. "NodeMathBlock": "m",
  180. "NodeTable": "t",
  181. "NodeBlockquote": "b",
  182. "NodeSuperBlock": "s",
  183. "NodeParagraph": "p",
  184. "NodeHTMLBlock": "html",
  185. "NodeBlockQueryEmbed": "query_embed",
  186. "NodeKramdownBlockIAL": "ial",
  187. "NodeIFrame": "iframe",
  188. "NodeWidget": "widget",
  189. "NodeThematicBreak": "tb",
  190. "NodeVideo": "video",
  191. "NodeAudio": "audio",
  192. // 行级元素
  193. "NodeText": "text",
  194. "NodeLinkText": "link_text",
  195. "NodeLinkDest": "link_dest",
  196. "NodeTag": "tag",
  197. "NodeCodeSpan": "code_span",
  198. "NodeInlineMath": "inline_math",
  199. "NodeBlockRefID": "ref_id",
  200. "NodeEmphasis": "em",
  201. "NodeStrong": "strong",
  202. "NodeStrikethrough": "strikethrough",
  203. "NodeMark": "mark",
  204. "NodeSup": "sup",
  205. "NodeSub": "sub",
  206. "NodeKbd": "kbd",
  207. "NodeUnderline": "underline",
  208. }
  209. var abbrTypeMap = map[string]string{}
  210. func init() {
  211. for typ, abbr := range typeAbbrMap {
  212. abbrTypeMap[abbr] = typ
  213. }
  214. }
  215. func TypeAbbr(nodeType string) string {
  216. return typeAbbrMap[nodeType]
  217. }
  218. func FromAbbrType(abbrType string) string {
  219. return abbrTypeMap[abbrType]
  220. }
  221. func SubTypeAbbr(n *ast.Node) string {
  222. switch n.Type {
  223. case ast.NodeList, ast.NodeListItem:
  224. if 0 == n.ListData.Typ {
  225. return "u"
  226. }
  227. if 1 == n.ListData.Typ {
  228. return "o"
  229. }
  230. if 3 == n.ListData.Typ {
  231. return "t"
  232. }
  233. case ast.NodeHeading:
  234. if 1 == n.HeadingLevel {
  235. return "h1"
  236. }
  237. if 2 == n.HeadingLevel {
  238. return "h2"
  239. }
  240. if 3 == n.HeadingLevel {
  241. return "h3"
  242. }
  243. if 4 == n.HeadingLevel {
  244. return "h4"
  245. }
  246. if 5 == n.HeadingLevel {
  247. return "h5"
  248. }
  249. if 6 == n.HeadingLevel {
  250. return "h6"
  251. }
  252. }
  253. return ""
  254. }
  255. func GetLegacyDynamicBlockRefDefIDs(node *ast.Node) (ret []string) {
  256. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  257. if !entering {
  258. return ast.WalkContinue
  259. }
  260. if ast.NodeBlockRefID == n.Type && ast.NodeCloseParen == n.Next.Type {
  261. ret = append(ret, n.TokensStr())
  262. return ast.WalkSkipChildren
  263. }
  264. return ast.WalkContinue
  265. })
  266. return
  267. }
  268. func SetDynamicBlockRefText(blockRef *ast.Node, refText string) {
  269. if nil == blockRef {
  270. return
  271. }
  272. idNode := blockRef.ChildByType(ast.NodeBlockRefID)
  273. if nil == idNode {
  274. return
  275. }
  276. var spacesRefTexts []*ast.Node // 可能会有多个空格,或者遗留错误插入的锚文本节点,这里做一次订正
  277. for n := idNode.Next; ast.NodeCloseParen != n.Type; n = n.Next {
  278. spacesRefTexts = append(spacesRefTexts, n)
  279. }
  280. for _, toRemove := range spacesRefTexts {
  281. toRemove.Unlink()
  282. }
  283. refText = strings.TrimSpace(refText)
  284. idNode.InsertAfter(&ast.Node{Type: ast.NodeBlockRefDynamicText, Tokens: []byte(refText)})
  285. idNode.InsertAfter(&ast.Node{Type: ast.NodeBlockRefSpace})
  286. }
  287. func GetDynamicBlockRefText(blockRef *ast.Node) string {
  288. refText := blockRef.ChildByType(ast.NodeBlockRefText)
  289. if nil != refText {
  290. return refText.Text()
  291. }
  292. refText = blockRef.ChildByType(ast.NodeBlockRefDynamicText)
  293. if nil != refText {
  294. return refText.Text()
  295. }
  296. return "ref resolve failed"
  297. }
  298. func IsChartCodeBlockCode(code *ast.Node) bool {
  299. if nil == code.Previous || ast.NodeCodeBlockFenceInfoMarker != code.Previous.Type || 1 > len(code.Previous.CodeBlockInfo) {
  300. return false
  301. }
  302. language := util.BytesToStr(code.Previous.CodeBlockInfo)
  303. language = strings.ReplaceAll(language, editor.Caret, "")
  304. return render.NoHighlight(language)
  305. }