From c913d576095f457ecd5796ca330188df57e292c2 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 18 Feb 2026 11:22:44 +0800 Subject: [PATCH] :art: Improve export block references to footnotes https://github.com/siyuan-note/siyuan/issues/17059 Signed-off-by: Daniel <845765@qq.com> --- kernel/model/export.go | 146 +++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 92 deletions(-) diff --git a/kernel/model/export.go b/kernel/model/export.go index 92aba438b..3c7f06a03 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -389,7 +389,7 @@ func Export2Liandi(id string) (err error) { ".md", 3, 1, 1, "#", "#", "", "", - false, false, nil, true, false, map[string]*parse.Tree{}) + false, false, nil, true, false) result := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() request = request. @@ -700,7 +700,7 @@ func ExportPreview(id string, fillCSSVar bool) (retStdHTML string) { blockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, "#", "#", // 这里固定使用 # 包裹标签,否则无法正确解析标签 https://github.com/siyuan-note/siyuan/issues/13857 Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true, map[string]*parse.Tree{}) + Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true) luteEngine := NewLute() enableLuteInlineSyntax(luteEngine) luteEngine.SetFootnotes(true) @@ -859,7 +859,7 @@ func ExportMarkdownHTML(id, savePath string, docx, merge bool) (name, dom string blockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true, map[string]*parse.Tree{}) + Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true) name = path.Base(tree.HPath) name = util.FilterFileName(name) // 导出 PDF、HTML 和 Word 时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/5614 savePath = strings.TrimSpace(savePath) @@ -1061,7 +1061,7 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do blockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true, map[string]*parse.Tree{}) + Conf.Export.AddTitle, Conf.Export.InlineMemo, true, true) adjustHeadingLevel(bt, tree) name = path.Base(tree.HPath) name = util.FilterFileName(name) // 导出 PDF、HTML 和 Word 时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/5614 @@ -1718,7 +1718,7 @@ func ExportStdMarkdown(id string, assetsDestSpace2Underscore, fillCSSVar, adjust ".md", Conf.Export.BlockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle, Conf.Export.InlineMemo, defBlockIDs, true, fillCSSVar, map[string]*parse.Tree{}) + Conf.Export.AddTitle, Conf.Export.InlineMemo, defBlockIDs, true, fillCSSVar) } func ExportPandocConvertZip(ids []string, pandocTo, ext string) (name, zipPath string) { @@ -1742,8 +1742,8 @@ func ExportPandocConvertZip(ids []string, pandocTo, ext string) (name, zipPath s } } - defBlockIDs, trees, docPaths := prepareExportTrees(docPaths) - zipPath = exportPandocConvertZip(baseFolderName, docPaths, defBlockIDs, "gfm+footnotes+hard_line_breaks", pandocTo, ext, trees) + defBlockIDs, docPaths := prepareExportTrees(docPaths) + zipPath = exportPandocConvertZip(baseFolderName, docPaths, defBlockIDs, "gfm+footnotes+hard_line_breaks", pandocTo, ext) name = util.GetTreeID(block.Path) return } @@ -1764,8 +1764,8 @@ func ExportNotebookMarkdown(boxID string) (zipPath string) { docPaths = append(docPaths, docFile.path) } - defBlockIDs, trees, docPaths := prepareExportTrees(docPaths) - zipPath = exportPandocConvertZip(box.Name, docPaths, defBlockIDs, "", "", ".md", trees) + defBlockIDs, docPaths := prepareExportTrees(docPaths) + zipPath = exportPandocConvertZip(box.Name, docPaths, defBlockIDs, "", "", ".md") return } @@ -1882,12 +1882,11 @@ func exportSYZip(boxID, rootDirPath, baseFolderName string, docPaths []string) ( } count := 1 - treeCache := map[string]*parse.Tree{} for _, tree := range trees { util.PushEndlessProgress(Conf.language(65) + " " + fmt.Sprintf(Conf.language(70), fmt.Sprintf("%d/%d %s", count, len(docPaths), tree.Root.IALAttr("title")))) refs := map[string]*parse.Tree{} - exportRefTrees(tree, &[]string{}, refs, treeCache) + exportRefTrees(tree, &[]string{}, refs) for refTreeID, refTree := range refs { if nil == trees[refTreeID] { refTrees[refTreeID] = refTree @@ -2234,7 +2233,7 @@ func ExportMarkdownContent(id string, refMode, embedMode int, addYfm, fillCSSVar ".md", refMode, embedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - addTitle, Conf.Export.InlineMemo, nil, true, fillCSSVar, map[string]*parse.Tree{}) + addTitle, Conf.Export.InlineMemo, nil, true, fillCSSVar) docIAL := parse.IAL2Map(tree.Root.KramdownIAL) if addYfm { exportedMd = yfm(docIAL) + exportedMd @@ -2242,8 +2241,8 @@ func ExportMarkdownContent(id string, refMode, embedMode int, addYfm, fillCSSVar return } -func exportMarkdownContent(rootID, ext string, exportRefMode int, defBlockIDs []string, singleFile bool, treeCache map[string]*parse.Tree) (tree *parse.Tree, exportedMd string, isEmpty bool) { - tree, err := loadTreeWithCache(rootID, treeCache) +func exportMarkdownContent(rootID, ext string, exportRefMode int, defBlockIDs []string, singleFile bool) (tree *parse.Tree, exportedMd string, isEmpty bool) { + tree, err := LoadTreeByBlockID(rootID) if err != nil { logging.LogErrorf("load tree by block id [%s] failed: %s", rootID, err) return @@ -2268,7 +2267,7 @@ func exportMarkdownContent(rootID, ext string, exportRefMode int, defBlockIDs [] ext, exportRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle, Conf.Export.InlineMemo, defBlockIDs, singleFile, false, treeCache) + Conf.Export.AddTitle, Conf.Export.InlineMemo, defBlockIDs, singleFile, false) docIAL := parse.IAL2Map(tree.Root.KramdownIAL) if Conf.Export.MarkdownYFM { // 导出 Markdown 时在文档头添加 YFM 开关 https://github.com/siyuan-note/siyuan/issues/7727 @@ -2280,12 +2279,12 @@ func exportMarkdownContent(rootID, ext string, exportRefMode int, defBlockIDs [] func exportMarkdownContent0(id string, tree *parse.Tree, cloudAssetsBase string, assetsDestSpace2Underscore, adjustHeadingLv, imgTag bool, ext string, blockRefMode, blockEmbedMode, fileAnnotationRefMode int, tagOpenMarker, tagCloseMarker string, blockRefTextLeft, blockRefTextRight string, - addTitle, inlineMemo bool, defBlockIDs []string, singleFile, fillCSSVar bool, treeCache map[string]*parse.Tree) (ret string) { + addTitle, inlineMemo bool, defBlockIDs []string, singleFile, fillCSSVar bool) (ret string) { tree = exportTree(tree, false, false, false, blockRefMode, blockEmbedMode, fileAnnotationRefMode, tagOpenMarker, tagCloseMarker, blockRefTextLeft, blockRefTextRight, - addTitle, inlineMemo, 0 < len(defBlockIDs), singleFile, treeCache) + addTitle, inlineMemo, 0 < len(defBlockIDs), singleFile) if adjustHeadingLv { bt := treenode.GetBlockTree(id) adjustHeadingLevel(bt, tree) @@ -2421,11 +2420,10 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool, blockRefMode, blockEmbedMode, fileAnnotationRefMode int, tagOpenMarker, tagCloseMarker string, blockRefTextLeft, blockRefTextRight string, - addTitle, inlineMemo, addDocAnchorSpan, singleFile bool, treeCache map[string]*parse.Tree) (ret *parse.Tree) { + addTitle, inlineMemo, addDocAnchorSpan, singleFile bool) (ret *parse.Tree) { luteEngine := NewLute() ret = tree id := tree.Root.ID - treeCache[tree.ID] = tree // 解析查询嵌入节点 depth := 0 @@ -2433,13 +2431,13 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool, // 将块超链接转换为引用 depth = 0 - blockLink2Ref(ret, ret.ID, treeCache, &depth) + blockLink2Ref(ret, ret.ID, &depth) // 收集引用转脚注+锚点哈希 var refFootnotes []*refAsFootnotes if 4 == blockRefMode && singleFile { depth = 0 - collectFootnotesDefs(ret, ret.ID, &refFootnotes, treeCache, &depth) + collectFootnotesDefs(ret, ret.ID, &refFootnotes, &depth) } currentTreeNodeIDs := map[string]bool{} @@ -2553,7 +2551,7 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool, if 4 == blockRefMode { // 脚注+锚点哈希 unlinks = nil - footnotesDefBlock := resolveFootnotesDefs(&refFootnotes, ret, currentTreeNodeIDs, blockRefTextLeft, blockRefTextRight, treeCache) + footnotesDefBlock := resolveFootnotesDefs(&refFootnotes, ret, currentTreeNodeIDs, blockRefTextLeft, blockRefTextRight) if nil != footnotesDefBlock { // 如果是聚焦导出,可能存在没有使用的脚注定义块,在这里进行清理 // Improve focus export conversion of block refs to footnotes https://github.com/siyuan-note/siyuan/issues/10647 @@ -3060,7 +3058,7 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool, return ret } -func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tree, currentTreeNodeIDs map[string]bool, blockRefTextLeft, blockRefTextRight string, treeCache map[string]*parse.Tree) (footnotesDefBlock *ast.Node) { +func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tree, currentTreeNodeIDs map[string]bool, blockRefTextLeft, blockRefTextRight string) (footnotesDefBlock *ast.Node) { if 1 > len(*refFootnotes) { return nil } @@ -3081,7 +3079,7 @@ func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tr continue } - t, err := loadTreeWithCache(bt.RootID, treeCache) + t, err := LoadTreeByBlockID(bt.RootID) if nil != err { logging.LogWarnf("load tree for footnote def [%s] refNum [%s] failed: %s", foot.defID, foot.refNum, err) continue @@ -3213,7 +3211,7 @@ func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tr return } -func blockLink2Ref(currentTree *parse.Tree, id string, treeCache map[string]*parse.Tree, depth *int) { +func blockLink2Ref(currentTree *parse.Tree, id string, depth *int) { *depth++ if 4096 < *depth { return @@ -3223,7 +3221,7 @@ func blockLink2Ref(currentTree *parse.Tree, id string, treeCache map[string]*par if nil == b { return } - t, err := loadTreeWithCache(b.RootID, treeCache) + t, err := LoadTreeByBlockID(b.RootID) if nil != err { return } @@ -3233,17 +3231,17 @@ func blockLink2Ref(currentTree *parse.Tree, id string, treeCache map[string]*par logging.LogErrorf("not found node [%s] in tree [%s]", b.ID, t.Root.ID) return } - blockLink2Ref0(currentTree, node, treeCache, depth) + blockLink2Ref0(currentTree, node, depth) if ast.NodeHeading == node.Type { children := treenode.HeadingChildren(node) for _, c := range children { - blockLink2Ref0(currentTree, c, treeCache, depth) + blockLink2Ref0(currentTree, c, depth) } } return } -func blockLink2Ref0(currentTree *parse.Tree, node *ast.Node, treeCache map[string]*parse.Tree, depth *int) { +func blockLink2Ref0(currentTree *parse.Tree, node *ast.Node, depth *int) { ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { if !entering { return ast.WalkContinue @@ -3254,17 +3252,17 @@ func blockLink2Ref0(currentTree *parse.Tree, node *ast.Node, treeCache map[strin n.TextMarkBlockRefID = strings.TrimPrefix(n.TextMarkAHref, "siyuan://blocks/") n.TextMarkBlockRefSubtype = "s" - blockLink2Ref(currentTree, n.TextMarkBlockRefID, treeCache, depth) + blockLink2Ref(currentTree, n.TextMarkBlockRefID, depth) return ast.WalkSkipChildren } else if treenode.IsBlockRef(n) { defID, _, _ := treenode.GetBlockRef(n) - blockLink2Ref(currentTree, defID, treeCache, depth) + blockLink2Ref(currentTree, defID, depth) } return ast.WalkContinue }) } -func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*refAsFootnotes, treeCache map[string]*parse.Tree, depth *int) { +func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*refAsFootnotes, depth *int) { *depth++ if 4096 < *depth { return @@ -3273,7 +3271,7 @@ func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*r if nil == b { return } - t, err := loadTreeWithCache(b.RootID, treeCache) + t, err := LoadTreeByBlockID(b.RootID) if nil != err { return } @@ -3283,17 +3281,17 @@ func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*r logging.LogErrorf("not found node [%s] in tree [%s]", b.ID, t.Root.ID) return } - collectFootnotesDefs0(currentTree, node, refFootnotes, treeCache, depth) + collectFootnotesDefs0(currentTree, node, refFootnotes, depth) if ast.NodeHeading == node.Type { children := treenode.HeadingChildren(node) for _, c := range children { - collectFootnotesDefs0(currentTree, c, refFootnotes, treeCache, depth) + collectFootnotesDefs0(currentTree, c, refFootnotes, depth) } } return } -func collectFootnotesDefs0(currentTree *parse.Tree, node *ast.Node, refFootnotes *[]*refAsFootnotes, treeCache map[string]*parse.Tree, depth *int) { +func collectFootnotesDefs0(currentTree *parse.Tree, node *ast.Node, refFootnotes *[]*refAsFootnotes, depth *int) { ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { if !entering { return ast.WalkContinue @@ -3315,7 +3313,7 @@ func collectFootnotesDefs0(currentTree *parse.Tree, node *ast.Node, refFootnotes refNum: strconv.Itoa(len(*refFootnotes) + 1), refAnchorText: anchorText, }) - collectFootnotesDefs(currentTree, defID, refFootnotes, treeCache, depth) + collectFootnotesDefs(currentTree, defID, refFootnotes, depth) } return ast.WalkSkipChildren } @@ -3400,8 +3398,7 @@ func processFileAnnotationRef(refID string, n *ast.Node, fileAnnotationRefMode i return ast.WalkSkipChildren } -func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []string, - pandocFrom, pandocTo, ext string, treeCache map[string]*parse.Tree) (zipPath string) { +func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []string, pandocFrom, pandocTo, ext string) (zipPath string) { defer util.ClearPushProgress(100) dir, name := path.Split(baseFolderName) @@ -3432,7 +3429,7 @@ func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []strin luteEngine := util.NewLute() for i, p := range docPaths { rootID := util.GetTreeID(p) - tree, md, isEmpty := exportMarkdownContent(rootID, ext, exportRefMode, defBlockIDs, false, treeCache) + tree, md, isEmpty := exportMarkdownContent(rootID, ext, exportRefMode, defBlockIDs, false) if nil == tree { continue } @@ -3617,9 +3614,8 @@ func getExportBlockRefLinkText(blockRef *ast.Node, blockRefTextLeft, blockRefTex return } -func prepareExportTrees(docPaths []string) (defBlockIDs []string, trees map[string]*parse.Tree, relatedDocPaths []string) { - trees = map[string]*parse.Tree{} - treeCache := map[string]*parse.Tree{} +func prepareExportTrees(docPaths []string) (defBlockIDs []string, relatedDocPaths []string) { + trees := map[string]*parse.Tree{} defBlockIDs = []string{} for i, p := range docPaths { rootID := strings.TrimSuffix(path.Base(p), ".sy") @@ -3627,11 +3623,11 @@ func prepareExportTrees(docPaths []string) (defBlockIDs []string, trees map[stri continue } - tree, err := loadTreeWithCache(rootID, treeCache) + tree, err := LoadTreeByBlockID(rootID) if err != nil { continue } - exportRefTrees(tree, &defBlockIDs, trees, treeCache) + exportRefTrees(tree, &defBlockIDs, trees) util.PushEndlessProgress(Conf.language(65) + " " + fmt.Sprintf(Conf.language(70), fmt.Sprintf("%d/%d %s", i+1, len(docPaths), tree.Root.IALAttr("title")))) } @@ -3643,7 +3639,7 @@ func prepareExportTrees(docPaths []string) (defBlockIDs []string, trees map[stri return } -func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache map[string]*parse.Tree) { +func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees map[string]*parse.Tree) { if nil != retTrees[tree.ID] { return } @@ -3664,23 +3660,16 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache return ast.WalkSkipChildren } - var defTree *parse.Tree - var err error - if treeCache[defBlock.RootID] != nil { - defTree = treeCache[defBlock.RootID] - } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) - if err != nil { - return ast.WalkSkipChildren - } - treeCache[defBlock.RootID] = defTree + defTree, err := LoadTreeByBlockID(defBlock.RootID) + if err != nil { + return ast.WalkSkipChildren } *defBlockIDs = append(*defBlockIDs, defID) if !Conf.Export.IncludeRelatedDocs { return ast.WalkSkipChildren } - exportRefTrees(defTree, defBlockIDs, retTrees, treeCache) + exportRefTrees(defTree, defBlockIDs, retTrees) } else if treenode.IsBlockLink(n) { defID := strings.TrimPrefix(n.TextMarkAHref, "siyuan://blocks/") if "" == defID { @@ -3691,23 +3680,16 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache return ast.WalkSkipChildren } - var defTree *parse.Tree - var err error - if treeCache[defBlock.RootID] != nil { - defTree = treeCache[defBlock.RootID] - } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) - if err != nil { - return ast.WalkSkipChildren - } - treeCache[defBlock.RootID] = defTree + defTree, err := LoadTreeByBlockID(defBlock.RootID) + if err != nil { + return ast.WalkSkipChildren } *defBlockIDs = append(*defBlockIDs, defID) if !Conf.Export.IncludeRelatedDocs { return ast.WalkSkipChildren } - exportRefTrees(defTree, defBlockIDs, retTrees, treeCache) + exportRefTrees(defTree, defBlockIDs, retTrees) } else if ast.NodeAttributeView == n.Type { // 导出数据库所在文档时一并导出绑定块所在文档 // Export the binding block docs when exporting the doc where the database is located https://github.com/siyuan-note/siyuan/issues/11486 @@ -3737,23 +3719,16 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache continue } - var defTree *parse.Tree - var err error - if treeCache[defBlock.RootID] != nil { - defTree = treeCache[defBlock.RootID] - } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) - if err != nil { - continue - } - treeCache[defBlock.RootID] = defTree + defTree, err := LoadTreeByBlockID(defBlock.RootID) + if err != nil { + continue } *defBlockIDs = append(*defBlockIDs, val.BlockID) if !Conf.Export.IncludeRelatedDocs { return ast.WalkSkipChildren } - exportRefTrees(defTree, defBlockIDs, retTrees, treeCache) + exportRefTrees(defTree, defBlockIDs, retTrees) } } return ast.WalkContinue @@ -3762,19 +3737,6 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache *defBlockIDs = gulu.Str.RemoveDuplicatedElem(*defBlockIDs) } -// loadTreeWithCache 加载树时优先从缓存获取,避免重复加载同一棵树 -// 注意传入的 rootID 必须是树的根节点 ID,不能是树中任意节点的 ID,否则会导致缓存失效和重复加载树 -func loadTreeWithCache(rootID string, treeCache map[string]*parse.Tree) (tree *parse.Tree, err error) { - if tree = treeCache[rootID]; nil != tree { - return - } - tree, err = LoadTreeByBlockID(rootID) - if nil == err && nil != tree { - treeCache[rootID] = tree - } - return -} - func getAttrViewTable(attrView *av.AttributeView, view *av.View, query string) (ret *av.Table) { switch view.LayoutType { case av.LayoutTypeGallery: