🎨 Improve handling of assets when exporting to Word .docx format https://github.com/siyuan-note/siyuan/issues/15253

Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
Daniel 2026-01-09 19:11:41 +08:00
parent cdf60c1105
commit ca24bea936
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
5 changed files with 51 additions and 40 deletions

View file

@ -177,7 +177,7 @@ func DocAssets(rootID string) (ret []string, err error) {
return
}
ret = getAssetsLinkDests(tree.Root)
ret = getAssetsLinkDests(tree.Root, false)
return
}
@ -521,7 +521,7 @@ func UploadAssets2Cloud(id string, ignorePushMsg bool) (count int, err error) {
var assets []string
for _, n := range nodes {
assets = append(assets, getAssetsLinkDests(n)...)
assets = append(assets, getAssetsLinkDests(n, false)...)
assets = append(assets, getQueryEmbedNodesAssetsLinkDests(n)...)
}
assets = gulu.Str.RemoveDuplicatedElem(assets)
@ -937,13 +937,13 @@ func UnusedAssets() (ret []string) {
trees = append(trees, tree)
}
for _, tree := range trees {
for _, d := range getAssetsLinkDests(tree.Root) {
for _, d := range getAssetsLinkDests(tree.Root, false) {
dests[d] = true
}
if titleImgPath := treenode.GetDocTitleImgPath(tree.Root); "" != titleImgPath {
// 题头图计入
if !util.IsAssetLinkDest([]byte(titleImgPath)) {
if !util.IsAssetLinkDest([]byte(titleImgPath), false) {
continue
}
dests[titleImgPath] = true
@ -1100,13 +1100,13 @@ func MissingAssets() (ret []string) {
trees = append(trees, tree)
}
for _, tree := range trees {
for _, d := range getAssetsLinkDests(tree.Root) {
for _, d := range getAssetsLinkDests(tree.Root, false) {
dests[d] = true
}
if titleImgPath := treenode.GetDocTitleImgPath(tree.Root); "" != titleImgPath {
// 题头图计入
if !util.IsAssetLinkDest([]byte(titleImgPath)) {
if !util.IsAssetLinkDest([]byte(titleImgPath), false) {
continue
}
dests[titleImgPath] = true
@ -1204,7 +1204,7 @@ func getQueryEmbedNodesAssetsLinkDests(node *ast.Node) (ret []string) {
continue
}
ret = append(ret, getAssetsLinkDests(embedNode)...)
ret = append(ret, getAssetsLinkDests(embedNode, false)...)
}
return ast.WalkContinue
})
@ -1212,7 +1212,7 @@ func getQueryEmbedNodesAssetsLinkDests(node *ast.Node) (ret []string) {
return
}
func getAssetsLinkDests(node *ast.Node) (ret []string) {
func getAssetsLinkDests(node *ast.Node, includePublic bool) (ret []string) {
ret = []string{}
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
if n.IsBlock() {
@ -1222,7 +1222,7 @@ func getAssetsLinkDests(node *ast.Node) (ret []string) {
k := kv[0]
if strings.HasPrefix(k, "custom-data-assets") {
dest := kv[1]
if "" == dest || !util.IsAssetLinkDest([]byte(dest)) {
if "" == dest || !util.IsAssetLinkDest([]byte(dest), includePublic) {
continue
}
ret = append(ret, dest)
@ -1238,21 +1238,21 @@ func getAssetsLinkDests(node *ast.Node) (ret []string) {
}
if ast.NodeLinkDest == n.Type {
if !util.IsAssetLinkDest(n.Tokens) {
if !util.IsAssetLinkDest(n.Tokens, includePublic) {
return ast.WalkContinue
}
dest := strings.TrimSpace(string(n.Tokens))
ret = append(ret, dest)
} else if n.IsTextMarkType("a") {
if !util.IsAssetLinkDest(gulu.Str.ToBytes(n.TextMarkAHref)) {
if !util.IsAssetLinkDest(gulu.Str.ToBytes(n.TextMarkAHref), includePublic) {
return ast.WalkContinue
}
dest := strings.TrimSpace(n.TextMarkAHref)
ret = append(ret, dest)
} else if n.IsTextMarkType("file-annotation-ref") {
if !util.IsAssetLinkDest(gulu.Str.ToBytes(n.TextMarkFileAnnotationRefID)) {
if !util.IsAssetLinkDest(gulu.Str.ToBytes(n.TextMarkFileAnnotationRefID), includePublic) {
return ast.WalkContinue
}
@ -1278,7 +1278,7 @@ func getAssetsLinkDests(node *ast.Node) (ret []string) {
for _, asset := range value.MAsset {
dest := asset.Content
if !util.IsAssetLinkDest([]byte(dest)) {
if !util.IsAssetLinkDest([]byte(dest), includePublic) {
continue
}
ret = append(ret, strings.TrimSpace(dest))
@ -1288,7 +1288,7 @@ func getAssetsLinkDests(node *ast.Node) (ret []string) {
for _, value := range keyValues.Values {
if nil != value.URL {
dest := value.URL.Content
if !util.IsAssetLinkDest([]byte(dest)) {
if !util.IsAssetLinkDest([]byte(dest), includePublic) {
continue
}
ret = append(ret, strings.TrimSpace(dest))
@ -1303,13 +1303,13 @@ func getAssetsLinkDests(node *ast.Node) (ret []string) {
// 兼容两种属性名 custom-data-assets 和 data-assets https://github.com/siyuan-note/siyuan/issues/4122#issuecomment-1154796568
dataAssets = n.IALAttr("data-assets")
}
if !util.IsAssetLinkDest([]byte(dataAssets)) {
if !util.IsAssetLinkDest([]byte(dataAssets), includePublic) {
return ast.WalkContinue
}
ret = append(ret, dataAssets)
} else { // HTMLBlock/InlineHTML/IFrame/Audio/Video
dest := treenode.GetNodeSrcTokens(n)
if !util.IsAssetLinkDest([]byte(dest)) {
if !util.IsAssetLinkDest([]byte(dest), includePublic) {
return ast.WalkContinue
}
ret = append(ret, dest)
@ -1379,7 +1379,7 @@ func getRemoteAssetsLinkDests(node *ast.Node, onlyImg bool) (ret []string) {
if onlyImg {
if ast.NodeLinkDest == node.Type {
if node.ParentIs(ast.NodeImage) {
if !util.IsAssetLinkDest(node.Tokens) {
if !util.IsAssetLinkDest(node.Tokens, false) {
ret = append(ret, string(node.Tokens))
}
@ -1406,7 +1406,7 @@ func getRemoteAssetsLinkDests(node *ast.Node, onlyImg bool) (ret []string) {
}
dest := asset.Content
if !util.IsAssetLinkDest([]byte(dest)) {
if !util.IsAssetLinkDest([]byte(dest), false) {
ret = append(ret, strings.TrimSpace(dest))
}
}
@ -1415,16 +1415,16 @@ func getRemoteAssetsLinkDests(node *ast.Node, onlyImg bool) (ret []string) {
}
} else {
if ast.NodeLinkDest == node.Type {
if !util.IsAssetLinkDest(node.Tokens) {
if !util.IsAssetLinkDest(node.Tokens, false) {
ret = append(ret, string(node.Tokens))
}
} else if node.IsTextMarkType("a") {
if !util.IsAssetLinkDest([]byte(node.TextMarkAHref)) {
if !util.IsAssetLinkDest([]byte(node.TextMarkAHref), false) {
ret = append(ret, node.TextMarkAHref)
}
} else if ast.NodeAudio == node.Type || ast.NodeVideo == node.Type {
src := treenode.GetNodeSrcTokens(node)
if !util.IsAssetLinkDest([]byte(src)) {
if !util.IsAssetLinkDest([]byte(src), false) {
ret = append(ret, src)
}
} else if ast.NodeAttributeView == node.Type {
@ -1445,7 +1445,7 @@ func getRemoteAssetsLinkDests(node *ast.Node, onlyImg bool) (ret []string) {
for _, asset := range value.MAsset {
dest := asset.Content
if !util.IsAssetLinkDest([]byte(dest)) {
if !util.IsAssetLinkDest([]byte(dest), false) {
ret = append(ret, strings.TrimSpace(dest))
}
}

View file

@ -276,7 +276,7 @@ func Export2Liandi(id string) (err error) {
return errors.New(Conf.Language(204))
}
assets := getAssetsLinkDests(tree.Root)
assets := getAssetsLinkDests(tree.Root, false)
embedAssets := getQueryEmbedNodesAssetsLinkDests(tree.Root)
assets = append(assets, embedAssets...)
assets = gulu.Str.RemoveDuplicatedElem(assets)
@ -795,9 +795,20 @@ func ExportMarkdownHTML(id, savePath string, docx, merge bool) (name, dom string
return
}
assets := getAssetsLinkDests(tree.Root)
if docx {
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if ast.NodeLinkDest == n.Type {
if bytes.HasPrefix(n.Tokens, []byte("file://")) {
n.Tokens = bytes.ReplaceAll(n.Tokens, []byte("\\"), []byte("/"))
}
}
return ast.WalkContinue
})
}
assets := getAssetsLinkDests(tree.Root, docx)
for _, asset := range assets {
if strings.HasPrefix(asset, "assets/") {
if strings.HasPrefix(asset, "assets/") || strings.HasPrefix(asset, "public/") {
if strings.Contains(asset, "?") {
asset = asset[:strings.LastIndex(asset, "?")]
}
@ -991,7 +1002,7 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do
return
}
assets := getAssetsLinkDests(tree.Root)
assets := getAssetsLinkDests(tree.Root, false)
for _, asset := range assets {
if strings.Contains(asset, "?") {
asset = asset[:strings.LastIndex(asset, "?")]
@ -1174,7 +1185,7 @@ func ProcessPDF(id, p string, merge, removeAssets, watermark bool) (err error) {
}
var headings []*ast.Node
assetDests := getAssetsLinkDests(tree.Root)
assetDests := getAssetsLinkDests(tree.Root, false)
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {
return ast.WalkContinue
@ -1874,10 +1885,10 @@ func exportSYZip(boxID, rootDirPath, baseFolderName string, docPaths []string) (
copiedAssets := hashset.New()
for _, tree := range trees {
var assets []string
assets = append(assets, getAssetsLinkDests(tree.Root)...)
assets = append(assets, getAssetsLinkDests(tree.Root, false)...)
titleImgPath := treenode.GetDocTitleImgPath(tree.Root) // Export .sy.zip doc title image is not exported https://github.com/siyuan-note/siyuan/issues/8748
if "" != titleImgPath {
if util.IsAssetLinkDest([]byte(titleImgPath)) {
if util.IsAssetLinkDest([]byte(titleImgPath), false) {
assets = append(assets, titleImgPath)
}
}
@ -2073,7 +2084,7 @@ func exportAv(avID, exportStorageAvDir, exportFolder string, assetPathMap map[st
case av.KeyTypeMAsset: // 导出资源文件列 https://github.com/siyuan-note/siyuan/issues/9919
for _, value := range keyValues.Values {
for _, asset := range value.MAsset {
if !util.IsAssetLinkDest([]byte(asset.Content)) {
if !util.IsAssetLinkDest([]byte(asset.Content), false) {
continue
}
@ -2218,17 +2229,17 @@ func exportMarkdownContent0(id string, tree *parse.Tree, cloudAssetsBase string,
}
if ast.NodeLinkDest == n.Type {
if util.IsAssetLinkDest(n.Tokens) {
if util.IsAssetLinkDest(n.Tokens, false) {
n.Tokens = bytes.ReplaceAll(n.Tokens, []byte(" "), []byte("_"))
}
} else if n.IsTextMarkType("a") {
href := n.TextMarkAHref
if util.IsAssetLinkDest([]byte(href)) {
if util.IsAssetLinkDest([]byte(href), false) {
n.TextMarkAHref = strings.ReplaceAll(href, " ", "_")
}
} else if ast.NodeIFrame == n.Type || ast.NodeAudio == n.Type || ast.NodeVideo == n.Type {
dest := treenode.GetNodeSrcTokens(n)
if util.IsAssetLinkDest([]byte(dest)) {
if util.IsAssetLinkDest([]byte(dest), false) {
setAssetsLinkDest(n, dest, strings.ReplaceAll(dest, " ", "_"))
}
}
@ -3353,7 +3364,7 @@ func exportPandocConvertZip(baseFolderName string, docPaths, defBlockIDs []strin
// 解析导出后的标准 Markdown汇总 assets
tree = parse.Parse("", gulu.Str.ToBytes(md), luteEngine.ParseOptions)
var assets []string
assets = append(assets, getAssetsLinkDests(tree.Root)...)
assets = append(assets, getAssetsLinkDests(tree.Root, false)...)
for _, asset := range assets {
asset = string(html.DecodeDestination([]byte(asset)))
if strings.Contains(asset, "?") {

View file

@ -65,7 +65,7 @@ func docTagSpans(n *ast.Node) (ret []*Span) {
func docTitleImgAsset(root *ast.Node, boxLocalPath, docDirLocalPath string) *Asset {
if p := treenode.GetDocTitleImgPath(root); "" != p {
if !util.IsAssetLinkDest([]byte(p)) {
if !util.IsAssetLinkDest([]byte(p), false) {
return nil
}

View file

@ -631,7 +631,7 @@ func buildSpanFromNode(n *ast.Node, tree *parse.Tree, rootID, boxID, p string) (
// assetsLinkDestsInTree
if !util.IsAssetLinkDest(destNode.Tokens) {
if !util.IsAssetLinkDest(destNode.Tokens, false) {
return
}
@ -686,7 +686,7 @@ func buildSpanFromNode(n *ast.Node, tree *parse.Tree, rootID, boxID, p string) (
if n.IsTextMarkType("a") {
dest := n.TextMarkAHref
if util.IsAssetLinkDest([]byte(dest)) {
if util.IsAssetLinkDest([]byte(dest), false) {
var title string
if titleNode := n.ChildByType(ast.NodeLinkTitle); nil != titleNode {
title = gulu.Str.FromBytes(titleNode.Tokens)
@ -776,7 +776,7 @@ func buildSpanFromNode(n *ast.Node, tree *parse.Tree, rootID, boxID, p string) (
return
}
if !util.IsAssetLinkDest(src) {
if !util.IsAssetLinkDest(src, false) {
walkStatus = ast.WalkContinue
return
}

View file

@ -316,8 +316,8 @@ func FilterSelfChildDocs(paths []string) (ret []string) {
return
}
func IsAssetLinkDest(dest []byte) bool {
return bytes.HasPrefix(dest, []byte("assets/"))
func IsAssetLinkDest(dest []byte, includePublic bool) bool {
return bytes.HasPrefix(dest, []byte("assets/")) || (includePublic && bytes.HasPrefix(dest, []byte("public/")))
}
var (