🎨 导入 Markdown 时将 Base64 编码的图片转换为文件 Fix https://github.com/siyuan-note/siyuan/issues/6671

This commit is contained in:
Liang Ding 2022-11-22 10:14:23 +08:00
parent 5f5d70740e
commit 31be236557
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
2 changed files with 108 additions and 7 deletions

View file

@ -30,6 +30,7 @@ import (
"time"
"github.com/88250/gulu"
"github.com/88250/lute"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/dustin/go-humanize"
@ -439,10 +440,30 @@ func moveTree(tree *parse.Tree) {
}
}
func parseStdMd(markdown []byte) (ret *parse.Tree) {
luteEngine := lute.New()
luteEngine.SetFootnotes(false)
luteEngine.SetToC(false)
luteEngine.SetIndentCodeBlock(false)
luteEngine.SetAutoSpace(false)
luteEngine.SetHeadingID(false)
luteEngine.SetSetext(false)
luteEngine.SetYamlFrontMatter(false)
luteEngine.SetLinkRef(false)
ret = parse.Parse("", markdown, luteEngine.ParseOptions)
genTreeID(ret)
return
}
func parseKTree(kramdown []byte) (ret *parse.Tree) {
luteEngine := NewLute()
ret = parse.Parse("", kramdown, luteEngine.ParseOptions)
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
genTreeID(ret)
return
}
func genTreeID(tree *parse.Tree) {
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {
return ast.WalkContinue
}
@ -475,7 +496,7 @@ func parseKTree(kramdown []byte) (ret *parse.Tree) {
}
return ast.WalkContinue
})
ret.Root.KramdownIAL = parse.Tokens2IAL(ret.Root.LastChild.Tokens)
tree.Root.KramdownIAL = parse.Tokens2IAL(tree.Root.LastChild.Tokens)
return
}

View file

@ -18,9 +18,13 @@ package model
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"io/fs"
"math/rand"
@ -403,6 +407,9 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
boxLocalPath = filepath.Join(util.DataDir, boxID)
base64TmpDir := filepath.Join(util.TempDir, "base64")
os.MkdirAll(base64TmpDir, 0755)
luteEngine := NewLute()
if gulu.File.IsDir(localPath) {
// 收集所有资源文件
@ -419,8 +426,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
if !strings.HasSuffix(info.Name(), ".md") && !strings.HasSuffix(info.Name(), ".markdown") {
dest := currentPath
assets[dest] = currentPath
assets[currentPath] = currentPath
return nil
}
return nil
@ -480,7 +486,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
return io.EOF
}
tree = parseKTree(data)
tree = parseStdMd(data)
if nil == tree {
logging.LogErrorf("parse tree [%s] failed", currentPath)
return nil
@ -507,6 +513,11 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
dest := n.TokensStr()
if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
processBase64Img(n, dest, base64TmpDir, assetDirPath, err)
return ast.WalkContinue
}
if !util.IsRelativePath(dest) || "" == dest {
return ast.WalkContinue
}
@ -570,7 +581,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
if nil != err {
return err
}
tree := parseKTree(data)
tree := parseStdMd(data)
if nil == tree {
msg := fmt.Sprintf("parse tree [%s] failed", localPath)
logging.LogErrorf(msg)
@ -595,6 +606,11 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
dest := n.TokensStr()
if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
processBase64Img(n, dest, base64TmpDir, assetDirPath, err)
return ast.WalkContinue
}
if !util.IsRelativePath(dest) {
return ast.WalkContinue
}
@ -641,6 +657,71 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
return
}
func processBase64Img(n *ast.Node, dest string, base64TmpDir string, assetDirPath string, err error) {
sep := strings.Index(dest, ";base64,")
var decodeErr error
unbased, decodeErr := base64.StdEncoding.DecodeString(dest[sep+8:])
if nil != decodeErr {
logging.LogErrorf("decode base64 image failed: %s", decodeErr)
return
}
dataReader := bytes.NewReader(unbased)
var img image.Image
var ext string
typ := dest[5:sep]
switch typ {
case "image/png":
img, decodeErr = png.Decode(dataReader)
ext = ".png"
case "image/jpeg":
img, decodeErr = jpeg.Decode(dataReader)
ext = ".jpg"
default:
logging.LogWarnf("unsupported base64 image type [%s]", typ)
return
}
if nil != decodeErr {
logging.LogErrorf("decode base64 image failed: %s", decodeErr)
return
}
name := "image" + ext
alt := n.Parent.ChildByType(ast.NodeLinkText)
if nil != alt {
name = alt.TokensStr() + ext
}
name = util.FilterFileName(name)
name = util.AssetName(name)
tmp := filepath.Join(base64TmpDir, name)
tmpFile, openErr := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE, 0644)
if nil != openErr {
logging.LogErrorf("open temp file [%s] failed: %s", tmp, openErr)
return
}
var encodeErr error
switch typ {
case "image/png":
encodeErr = png.Encode(tmpFile, img)
case "image/jpeg":
encodeErr = jpeg.Encode(tmpFile, img, &jpeg.Options{Quality: 100})
}
if nil != encodeErr {
logging.LogErrorf("encode base64 image failed: %s", encodeErr)
tmpFile.Close()
return
}
tmpFile.Close()
assetTargetPath := filepath.Join(assetDirPath, name)
if err = filelock.Copy(tmp, assetTargetPath); nil != err {
logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", tmp, assetTargetPath, err)
return
}
n.Tokens = []byte("assets/" + name)
}
func imgHtmlBlock2InlineImg(tree *parse.Tree) {
imgHtmlBlocks := map[*ast.Node]*html.Node{}
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
@ -691,7 +772,6 @@ func imgHtmlBlock2InlineImg(tree *parse.Tree) {
n.InsertBefore(p)
n.Unlink()
}
return
}