import.go 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. // SiYuan - Refactor your thinking
  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 model
  17. import (
  18. "bytes"
  19. "encoding/base64"
  20. "encoding/json"
  21. "errors"
  22. "fmt"
  23. "image"
  24. "image/jpeg"
  25. "image/png"
  26. "io"
  27. "io/fs"
  28. "math/rand"
  29. "os"
  30. "path"
  31. "path/filepath"
  32. "regexp"
  33. "runtime/debug"
  34. "sort"
  35. "strconv"
  36. "strings"
  37. "time"
  38. "github.com/88250/gulu"
  39. "github.com/88250/lute/ast"
  40. "github.com/88250/lute/html"
  41. "github.com/88250/lute/html/atom"
  42. "github.com/88250/lute/parse"
  43. "github.com/88250/lute/render"
  44. "github.com/siyuan-note/filelock"
  45. "github.com/siyuan-note/logging"
  46. "github.com/siyuan-note/riff"
  47. "github.com/siyuan-note/siyuan/kernel/av"
  48. "github.com/siyuan-note/siyuan/kernel/cache"
  49. "github.com/siyuan-note/siyuan/kernel/filesys"
  50. "github.com/siyuan-note/siyuan/kernel/sql"
  51. "github.com/siyuan-note/siyuan/kernel/treenode"
  52. "github.com/siyuan-note/siyuan/kernel/util"
  53. )
  54. func HTML2Markdown(htmlStr string) (markdown string, err error) {
  55. assetDirPath := filepath.Join(util.DataDir, "assets")
  56. luteEngine := util.NewLute()
  57. tree := luteEngine.HTML2Tree(htmlStr)
  58. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  59. if !entering || ast.NodeLinkDest != n.Type {
  60. return ast.WalkContinue
  61. }
  62. dest := n.TokensStr()
  63. if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
  64. processBase64Img(n, dest, assetDirPath, err)
  65. return ast.WalkContinue
  66. }
  67. return ast.WalkContinue
  68. })
  69. var formatted []byte
  70. renderer := render.NewFormatRenderer(tree, luteEngine.RenderOptions)
  71. for nodeType, rendererFunc := range luteEngine.HTML2MdRendererFuncs {
  72. renderer.ExtRendererFuncs[nodeType] = rendererFunc
  73. }
  74. formatted = renderer.Render()
  75. markdown = gulu.Str.FromBytes(formatted)
  76. return
  77. }
  78. func ImportSY(zipPath, boxID, toPath string) (err error) {
  79. util.PushEndlessProgress(Conf.Language(73))
  80. defer util.ClearPushProgress(100)
  81. lockSync()
  82. defer unlockSync()
  83. baseName := filepath.Base(zipPath)
  84. ext := filepath.Ext(baseName)
  85. baseName = strings.TrimSuffix(baseName, ext)
  86. unzipPath := filepath.Join(filepath.Dir(zipPath), baseName+"-"+gulu.Rand.String(7))
  87. err = gulu.Zip.Unzip(zipPath, unzipPath)
  88. if nil != err {
  89. return
  90. }
  91. defer os.RemoveAll(unzipPath)
  92. var syPaths []string
  93. filelock.Walk(unzipPath, func(path string, info fs.FileInfo, err error) error {
  94. if nil != err {
  95. return err
  96. }
  97. if !info.IsDir() && strings.HasSuffix(info.Name(), ".sy") {
  98. syPaths = append(syPaths, path)
  99. }
  100. return nil
  101. })
  102. entries, err := os.ReadDir(unzipPath)
  103. if nil != err {
  104. logging.LogErrorf("read unzip dir [%s] failed: %s", unzipPath, err)
  105. return
  106. }
  107. if 1 != len(entries) {
  108. logging.LogErrorf("invalid .sy.zip [%v]", entries)
  109. return errors.New(Conf.Language(199))
  110. }
  111. unzipRootPath := filepath.Join(unzipPath, entries[0].Name())
  112. name := filepath.Base(unzipRootPath)
  113. if strings.HasPrefix(name, "data-20") && len("data-20230321175442") == len(name) {
  114. logging.LogErrorf("invalid .sy.zip [unzipRootPath=%s, baseName=%s]", unzipRootPath, name)
  115. return errors.New(Conf.Language(199))
  116. }
  117. luteEngine := util.NewLute()
  118. blockIDs := map[string]string{}
  119. avBlockIDs := map[string]string{}
  120. trees := map[string]*parse.Tree{}
  121. // 重新生成块 ID
  122. for _, syPath := range syPaths {
  123. data, readErr := os.ReadFile(syPath)
  124. if nil != readErr {
  125. logging.LogErrorf("read .sy [%s] failed: %s", syPath, readErr)
  126. err = readErr
  127. return
  128. }
  129. tree, _, parseErr := filesys.ParseJSON(data, luteEngine.ParseOptions)
  130. if nil != parseErr {
  131. logging.LogErrorf("parse .sy [%s] failed: %s", syPath, parseErr)
  132. err = parseErr
  133. return
  134. }
  135. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  136. if !entering || "" == n.ID {
  137. return ast.WalkContinue
  138. }
  139. // 新 ID 保留时间部分,仅修改随机值,避免时间变化导致更新时间早于创建时间
  140. // Keep original creation time when importing .sy.zip https://github.com/siyuan-note/siyuan/issues/9923
  141. newNodeID := util.TimeFromID(n.ID) + "-" + util.RandString(7)
  142. blockIDs[n.ID] = newNodeID
  143. oldNodeID := n.ID
  144. n.ID = newNodeID
  145. n.SetIALAttr("id", newNodeID)
  146. // 重新指向数据库属性值
  147. for _, kv := range n.KramdownIAL {
  148. if 2 > len(kv) {
  149. continue
  150. }
  151. if strings.HasPrefix(kv[0], av.NodeAttrNameAvs) {
  152. avBlockIDs[oldNodeID] = newNodeID
  153. }
  154. }
  155. return ast.WalkContinue
  156. })
  157. tree.ID = tree.Root.ID
  158. tree.Path = filepath.ToSlash(strings.TrimPrefix(syPath, unzipRootPath))
  159. trees[tree.ID] = tree
  160. }
  161. // 引用和嵌入指向重新生成的块 ID
  162. for _, tree := range trees {
  163. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  164. if !entering {
  165. return ast.WalkContinue
  166. }
  167. if treenode.IsBlockRef(n) {
  168. defID, _, _ := treenode.GetBlockRef(n)
  169. newDefID := blockIDs[defID]
  170. if "" != newDefID {
  171. n.TextMarkBlockRefID = newDefID
  172. }
  173. } else if ast.NodeTextMark == n.Type && n.IsTextMarkType("a") && strings.HasPrefix(n.TextMarkAHref, "siyuan://blocks/") {
  174. // Block hyperlinks do not point to regenerated block IDs when importing .sy.zip https://github.com/siyuan-note/siyuan/issues/9083
  175. defID := strings.TrimPrefix(n.TextMarkAHref, "siyuan://blocks/")
  176. newDefID := blockIDs[defID]
  177. if "" != newDefID {
  178. n.TextMarkAHref = "siyuan://blocks/" + newDefID
  179. }
  180. } else if ast.NodeBlockQueryEmbedScript == n.Type {
  181. for oldID, newID := range blockIDs {
  182. // 导入 `.sy.zip` 后查询嵌入块失效 https://github.com/siyuan-note/siyuan/issues/5316
  183. n.Tokens = bytes.ReplaceAll(n.Tokens, []byte(oldID), []byte(newID))
  184. }
  185. }
  186. return ast.WalkContinue
  187. })
  188. }
  189. // 将关联的数据库文件移动到 data/storage/av/ 下
  190. storage := filepath.Join(unzipRootPath, "storage")
  191. storageAvDir := filepath.Join(storage, "av")
  192. avIDs := map[string]string{}
  193. renameAvPaths := map[string]string{}
  194. if gulu.File.IsExist(storageAvDir) {
  195. // 重新生成数据库数据
  196. filelock.Walk(storageAvDir, func(path string, info fs.FileInfo, err error) error {
  197. if !strings.HasSuffix(path, ".json") || !ast.IsNodeIDPattern(strings.TrimSuffix(info.Name(), ".json")) {
  198. return nil
  199. }
  200. // 重命名数据库
  201. newAvID := ast.NewNodeID()
  202. oldAvID := strings.TrimSuffix(info.Name(), ".json")
  203. newPath := filepath.Join(filepath.Dir(path), newAvID+".json")
  204. renameAvPaths[path] = newPath
  205. avIDs[oldAvID] = newAvID
  206. return nil
  207. })
  208. // 重命名数据库文件
  209. for oldPath, newPath := range renameAvPaths {
  210. data, readErr := os.ReadFile(oldPath)
  211. if nil != readErr {
  212. logging.LogErrorf("read av file [%s] failed: %s", oldPath, readErr)
  213. return nil
  214. }
  215. // 将数据库文件中的块 ID 替换为新的块 ID
  216. newData := data
  217. for oldAvID, newAvID := range avIDs {
  218. for oldID, newID := range avBlockIDs {
  219. newData = bytes.ReplaceAll(newData, []byte(oldID), []byte(newID))
  220. }
  221. newData = bytes.ReplaceAll(newData, []byte(oldAvID), []byte(newAvID))
  222. }
  223. if !bytes.Equal(data, newData) {
  224. if writeErr := os.WriteFile(oldPath, newData, 0644); nil != writeErr {
  225. logging.LogErrorf("write av file [%s] failed: %s", oldPath, writeErr)
  226. return nil
  227. }
  228. }
  229. if err = os.Rename(oldPath, newPath); nil != err {
  230. logging.LogErrorf("rename av file from [%s] to [%s] failed: %s", oldPath, newPath, err)
  231. return
  232. }
  233. }
  234. targetStorageAvDir := filepath.Join(util.DataDir, "storage", "av")
  235. if copyErr := filelock.Copy(storageAvDir, targetStorageAvDir); nil != copyErr {
  236. logging.LogErrorf("copy storage av dir from [%s] to [%s] failed: %s", storageAvDir, targetStorageAvDir, copyErr)
  237. }
  238. // 重新指向数据库属性值
  239. for _, tree := range trees {
  240. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  241. if !entering || "" == n.ID {
  242. return ast.WalkContinue
  243. }
  244. ial := parse.IAL2Map(n.KramdownIAL)
  245. for k, v := range ial {
  246. if strings.HasPrefix(k, av.NodeAttrNameAvs) {
  247. newKey, newVal := k, v
  248. for oldAvID, newAvID := range avIDs {
  249. newKey = strings.ReplaceAll(newKey, oldAvID, newAvID)
  250. newVal = strings.ReplaceAll(newVal, oldAvID, newAvID)
  251. }
  252. n.RemoveIALAttr(k)
  253. n.SetIALAttr(newKey, newVal)
  254. }
  255. }
  256. if ast.NodeAttributeView == n.Type {
  257. n.AttributeViewID = avIDs[n.AttributeViewID]
  258. }
  259. return ast.WalkContinue
  260. })
  261. // 关联数据库和块
  262. avNodes := tree.Root.ChildrenByType(ast.NodeAttributeView)
  263. av.BatchUpsertBlockRel(avNodes)
  264. }
  265. // 如果数据库中绑定的块不在导入的文档中
  266. cachedTrees, saveTrees := map[string]*parse.Tree{}, map[string]*parse.Tree{}
  267. for _, avID := range avIDs {
  268. attrView, _ := av.ParseAttributeView(avID)
  269. if nil == attrView {
  270. continue
  271. }
  272. blockKeyValues := attrView.GetBlockKeyValues()
  273. for _, blockValue := range blockKeyValues.Values {
  274. if blockValue.IsDetached {
  275. continue
  276. }
  277. bt := treenode.GetBlockTree(blockValue.BlockID)
  278. if nil == bt {
  279. continue
  280. }
  281. tree := cachedTrees[bt.RootID]
  282. if nil == tree {
  283. tree, _ = filesys.LoadTree(bt.BoxID, bt.Path, luteEngine)
  284. if nil == tree {
  285. continue
  286. }
  287. cachedTrees[bt.RootID] = tree
  288. }
  289. node := treenode.GetNodeInTree(tree, blockValue.BlockID)
  290. if nil == node {
  291. continue
  292. }
  293. attrs := parse.IAL2Map(node.KramdownIAL)
  294. if "" == attrs[av.NodeAttrNameAvs] {
  295. attrs[av.NodeAttrNameAvs] = avID
  296. } else {
  297. nodeAvIDs := strings.Split(attrs[av.NodeAttrNameAvs], ",")
  298. nodeAvIDs = append(nodeAvIDs, avID)
  299. nodeAvIDs = gulu.Str.RemoveDuplicatedElem(nodeAvIDs)
  300. attrs[av.NodeAttrNameAvs] = strings.Join(nodeAvIDs, ",")
  301. saveTrees[bt.RootID] = tree
  302. }
  303. avNames := getAvNames(attrs[av.NodeAttrNameAvs])
  304. if "" != avNames {
  305. attrs[av.NodeAttrViewNames] = avNames
  306. }
  307. oldAttrs, setErr := setNodeAttrs0(node, attrs)
  308. if nil != setErr {
  309. continue
  310. }
  311. cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
  312. pushBroadcastAttrTransactions(oldAttrs, node)
  313. }
  314. }
  315. for _, saveTree := range saveTrees {
  316. if treeErr := indexWriteTreeUpsertQueue(saveTree); nil != treeErr {
  317. logging.LogErrorf("index write tree upsert queue failed: %s", treeErr)
  318. }
  319. avNodes := saveTree.Root.ChildrenByType(ast.NodeAttributeView)
  320. av.BatchUpsertBlockRel(avNodes)
  321. }
  322. }
  323. // 将关联的闪卡数据合并到默认卡包 data/storage/riff/20230218211946-2kw8jgx 中
  324. storageRiffDir := filepath.Join(storage, "riff")
  325. if gulu.File.IsExist(storageRiffDir) {
  326. deckToImport, loadErr := riff.LoadDeck(storageRiffDir, builtinDeckID, Conf.Flashcard.RequestRetention, Conf.Flashcard.MaximumInterval, Conf.Flashcard.Weights)
  327. if nil != loadErr {
  328. logging.LogErrorf("load deck [%s] failed: %s", name, loadErr)
  329. } else {
  330. deck := Decks[builtinDeckID]
  331. if nil == deck {
  332. var createErr error
  333. deck, createErr = createDeck0("Built-in Deck", builtinDeckID)
  334. if nil == createErr {
  335. Decks[deck.ID] = deck
  336. }
  337. }
  338. bIDs := deckToImport.GetBlockIDs()
  339. cards := deckToImport.GetCardsByBlockIDs(bIDs)
  340. for _, card := range cards {
  341. deck.AddCard(card.ID(), blockIDs[card.BlockID()])
  342. }
  343. if 0 < len(cards) {
  344. if saveErr := deck.Save(); nil != saveErr {
  345. logging.LogErrorf("save deck [%s] failed: %s", name, saveErr)
  346. }
  347. }
  348. }
  349. }
  350. // storage 文件夹已在上方处理,所以这里删除源 storage 文件夹,避免后面被拷贝到导入目录下 targetDir
  351. if removeErr := os.RemoveAll(storage); nil != removeErr {
  352. logging.LogErrorf("remove temp storage av dir failed: %s", removeErr)
  353. }
  354. // 写回 .sy
  355. for _, tree := range trees {
  356. syPath := filepath.Join(unzipRootPath, tree.Path)
  357. if "" == tree.Root.Spec {
  358. parse.NestedInlines2FlattedSpans(tree, false)
  359. tree.Root.Spec = "1"
  360. }
  361. renderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions)
  362. data := renderer.Render()
  363. if !util.UseSingleLineSave {
  364. buf := bytes.Buffer{}
  365. buf.Grow(1024 * 1024 * 2)
  366. if err = json.Indent(&buf, data, "", "\t"); nil != err {
  367. return
  368. }
  369. data = buf.Bytes()
  370. }
  371. if err = os.WriteFile(syPath, data, 0644); nil != err {
  372. logging.LogErrorf("write .sy [%s] failed: %s", syPath, err)
  373. return
  374. }
  375. newSyPath := filepath.Join(filepath.Dir(syPath), tree.ID+".sy")
  376. if err = filelock.Rename(syPath, newSyPath); nil != err {
  377. logging.LogErrorf("rename .sy from [%s] to [%s] failed: %s", syPath, newSyPath, err)
  378. return
  379. }
  380. }
  381. // 合并 sort.json
  382. fullSortIDs := map[string]int{}
  383. sortIDs := map[string]int{}
  384. var sortData []byte
  385. var sortErr error
  386. sortPath := filepath.Join(unzipRootPath, ".siyuan", "sort.json")
  387. if filelock.IsExist(sortPath) {
  388. sortData, sortErr = filelock.ReadFile(sortPath)
  389. if nil != sortErr {
  390. logging.LogErrorf("read import sort conf failed: %s", sortErr)
  391. }
  392. if sortErr = gulu.JSON.UnmarshalJSON(sortData, &sortIDs); nil != sortErr {
  393. logging.LogErrorf("unmarshal sort conf failed: %s", sortErr)
  394. }
  395. boxSortPath := filepath.Join(util.DataDir, boxID, ".siyuan", "sort.json")
  396. if filelock.IsExist(boxSortPath) {
  397. sortData, sortErr = filelock.ReadFile(boxSortPath)
  398. if nil != sortErr {
  399. logging.LogErrorf("read box sort conf failed: %s", sortErr)
  400. }
  401. if sortErr = gulu.JSON.UnmarshalJSON(sortData, &fullSortIDs); nil != sortErr {
  402. logging.LogErrorf("unmarshal box sort conf failed: %s", sortErr)
  403. }
  404. }
  405. for oldID, sort := range sortIDs {
  406. if newID := blockIDs[oldID]; "" != newID {
  407. fullSortIDs[newID] = sort
  408. }
  409. }
  410. sortData, sortErr = gulu.JSON.MarshalJSON(fullSortIDs)
  411. if nil != sortErr {
  412. logging.LogErrorf("marshal box full sort conf failed: %s", sortErr)
  413. } else {
  414. sortErr = filelock.WriteFile(boxSortPath, sortData)
  415. if nil != sortErr {
  416. logging.LogErrorf("write box full sort conf failed: %s", sortErr)
  417. }
  418. }
  419. if removeErr := os.RemoveAll(sortPath); nil != removeErr {
  420. logging.LogErrorf("remove temp sort conf failed: %s", removeErr)
  421. }
  422. }
  423. // 重命名文件路径
  424. renamePaths := map[string]string{}
  425. filelock.Walk(unzipRootPath, func(path string, info fs.FileInfo, err error) error {
  426. if nil != err {
  427. return err
  428. }
  429. if info.IsDir() && ast.IsNodeIDPattern(info.Name()) {
  430. renamePaths[path] = path
  431. }
  432. return nil
  433. })
  434. for p, _ := range renamePaths {
  435. originalPath := p
  436. p = strings.TrimPrefix(p, unzipRootPath)
  437. p = filepath.ToSlash(p)
  438. parts := strings.Split(p, "/")
  439. buf := bytes.Buffer{}
  440. buf.WriteString("/")
  441. for i, part := range parts {
  442. if "" == part {
  443. continue
  444. }
  445. newNodeID := blockIDs[part]
  446. if "" != newNodeID {
  447. buf.WriteString(newNodeID)
  448. } else {
  449. buf.WriteString(part)
  450. }
  451. if i < len(parts)-1 {
  452. buf.WriteString("/")
  453. }
  454. }
  455. newPath := buf.String()
  456. renamePaths[originalPath] = filepath.Join(unzipRootPath, newPath)
  457. }
  458. var oldPaths []string
  459. for oldPath, _ := range renamePaths {
  460. oldPaths = append(oldPaths, oldPath)
  461. }
  462. sort.Slice(oldPaths, func(i, j int) bool {
  463. return strings.Count(oldPaths[i], string(os.PathSeparator)) < strings.Count(oldPaths[j], string(os.PathSeparator))
  464. })
  465. for i, oldPath := range oldPaths {
  466. newPath := renamePaths[oldPath]
  467. if err = filelock.Rename(oldPath, newPath); nil != err {
  468. logging.LogErrorf("rename path from [%s] to [%s] failed: %s", oldPath, renamePaths[oldPath], err)
  469. return errors.New("rename path failed")
  470. }
  471. delete(renamePaths, oldPath)
  472. var toRemoves []string
  473. newRenamedPaths := map[string]string{}
  474. for oldP, newP := range renamePaths {
  475. if strings.HasPrefix(oldP, oldPath) {
  476. renamedOldP := strings.Replace(oldP, oldPath, newPath, 1)
  477. newRenamedPaths[renamedOldP] = newP
  478. toRemoves = append(toRemoves, oldPath)
  479. }
  480. }
  481. for _, toRemove := range toRemoves {
  482. delete(renamePaths, toRemove)
  483. }
  484. for oldP, newP := range newRenamedPaths {
  485. renamePaths[oldP] = newP
  486. }
  487. for j := i + 1; j < len(oldPaths); j++ {
  488. if strings.HasPrefix(oldPaths[j], oldPath) {
  489. renamedOldP := strings.Replace(oldPaths[j], oldPath, newPath, 1)
  490. oldPaths[j] = renamedOldP
  491. }
  492. }
  493. }
  494. // 将包含的资源文件统一移动到 data/assets/ 下
  495. var assetsDirs []string
  496. filelock.Walk(unzipRootPath, func(path string, info fs.FileInfo, err error) error {
  497. if strings.Contains(path, "assets") && info.IsDir() {
  498. assetsDirs = append(assetsDirs, path)
  499. }
  500. return nil
  501. })
  502. dataAssets := filepath.Join(util.DataDir, "assets")
  503. for _, assets := range assetsDirs {
  504. if gulu.File.IsDir(assets) {
  505. if err = filelock.Copy(assets, dataAssets); nil != err {
  506. logging.LogErrorf("copy assets from [%s] to [%s] failed: %s", assets, dataAssets, err)
  507. return
  508. }
  509. }
  510. os.RemoveAll(assets)
  511. }
  512. var baseTargetPath string
  513. if "/" == toPath {
  514. baseTargetPath = "/"
  515. } else {
  516. block := treenode.GetBlockTreeRootByPath(boxID, toPath)
  517. if nil == block {
  518. logging.LogErrorf("not found block by path [%s]", toPath)
  519. return nil
  520. }
  521. baseTargetPath = strings.TrimSuffix(block.Path, ".sy")
  522. }
  523. targetDir := filepath.Join(util.DataDir, boxID, baseTargetPath)
  524. if err = os.MkdirAll(targetDir, 0755); nil != err {
  525. return
  526. }
  527. var treePaths []string
  528. filelock.Walk(unzipRootPath, func(path string, info fs.FileInfo, err error) error {
  529. if info.IsDir() {
  530. if strings.HasPrefix(info.Name(), ".") {
  531. return filepath.SkipDir
  532. }
  533. return nil
  534. }
  535. if !strings.HasSuffix(info.Name(), ".sy") {
  536. return nil
  537. }
  538. p := strings.TrimPrefix(path, unzipRootPath)
  539. p = filepath.ToSlash(p)
  540. treePaths = append(treePaths, p)
  541. return nil
  542. })
  543. if err = filelock.Copy(unzipRootPath, targetDir); nil != err {
  544. logging.LogErrorf("copy data dir from [%s] to [%s] failed: %s", unzipRootPath, util.DataDir, err)
  545. err = errors.New("copy data failed")
  546. return
  547. }
  548. boxAbsPath := filepath.Join(util.DataDir, boxID)
  549. for _, treePath := range treePaths {
  550. absPath := filepath.Join(targetDir, treePath)
  551. p := strings.TrimPrefix(absPath, boxAbsPath)
  552. p = filepath.ToSlash(p)
  553. tree, err := filesys.LoadTree(boxID, p, luteEngine)
  554. if nil != err {
  555. logging.LogErrorf("load tree [%s] failed: %s", treePath, err)
  556. continue
  557. }
  558. treenode.IndexBlockTree(tree)
  559. sql.IndexTreeQueue(tree)
  560. }
  561. IncSync()
  562. return
  563. }
  564. func ImportData(zipPath string) (err error) {
  565. util.PushEndlessProgress(Conf.Language(73))
  566. defer util.ClearPushProgress(100)
  567. lockSync()
  568. defer unlockSync()
  569. baseName := filepath.Base(zipPath)
  570. ext := filepath.Ext(baseName)
  571. baseName = strings.TrimSuffix(baseName, ext)
  572. unzipPath := filepath.Join(filepath.Dir(zipPath), baseName)
  573. err = gulu.Zip.Unzip(zipPath, unzipPath)
  574. if nil != err {
  575. return
  576. }
  577. defer os.RemoveAll(unzipPath)
  578. files, err := filepath.Glob(filepath.Join(unzipPath, "*/*.sy"))
  579. if nil != err {
  580. logging.LogErrorf("check data.zip failed: %s", err)
  581. return errors.New("check data.zip failed")
  582. }
  583. if 0 < len(files) {
  584. return errors.New(Conf.Language(198))
  585. }
  586. dirs, err := os.ReadDir(unzipPath)
  587. if nil != err {
  588. logging.LogErrorf("check data.zip failed: %s", err)
  589. return errors.New("check data.zip failed")
  590. }
  591. if 1 != len(dirs) {
  592. return errors.New(Conf.Language(198))
  593. }
  594. tmpDataPath := filepath.Join(unzipPath, dirs[0].Name())
  595. if err = filelock.Copy(tmpDataPath, util.DataDir); nil != err {
  596. logging.LogErrorf("copy data dir from [%s] to [%s] failed: %s", tmpDataPath, util.DataDir, err)
  597. err = errors.New("copy data failed")
  598. return
  599. }
  600. IncSync()
  601. FullReindex()
  602. return
  603. }
  604. func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
  605. util.PushEndlessProgress(Conf.Language(73))
  606. defer func() {
  607. util.PushClearProgress()
  608. if e := recover(); nil != e {
  609. stack := debug.Stack()
  610. msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, stack)
  611. logging.LogErrorf("import from local path failed: %s", msg)
  612. err = errors.New("import from local path failed, please check kernel log for details")
  613. }
  614. }()
  615. lockSync()
  616. defer unlockSync()
  617. WaitForWritingFiles()
  618. var baseHPath, baseTargetPath, boxLocalPath string
  619. if "/" == toPath {
  620. baseHPath = "/"
  621. baseTargetPath = "/"
  622. } else {
  623. block := treenode.GetBlockTreeRootByPath(boxID, toPath)
  624. if nil == block {
  625. logging.LogErrorf("not found block by path [%s]", toPath)
  626. return nil
  627. }
  628. baseHPath = block.HPath
  629. baseTargetPath = strings.TrimSuffix(block.Path, ".sy")
  630. }
  631. boxLocalPath = filepath.Join(util.DataDir, boxID)
  632. if gulu.File.IsDir(localPath) {
  633. // 收集所有资源文件
  634. assets := map[string]string{}
  635. filelock.Walk(localPath, func(currentPath string, info os.FileInfo, walkErr error) error {
  636. if localPath == currentPath {
  637. return nil
  638. }
  639. if strings.HasPrefix(info.Name(), ".") {
  640. if info.IsDir() {
  641. return filepath.SkipDir
  642. }
  643. return nil
  644. }
  645. if !strings.HasSuffix(info.Name(), ".md") && !strings.HasSuffix(info.Name(), ".markdown") {
  646. assets[currentPath] = currentPath
  647. return nil
  648. }
  649. return nil
  650. })
  651. targetPaths := map[string]string{}
  652. assetsDone := map[string]string{}
  653. // md 转换 sy
  654. filelock.Walk(localPath, func(currentPath string, info os.FileInfo, walkErr error) error {
  655. if strings.HasPrefix(info.Name(), ".") {
  656. if info.IsDir() {
  657. return filepath.SkipDir
  658. }
  659. return nil
  660. }
  661. var tree *parse.Tree
  662. var ext string
  663. title := info.Name()
  664. if !info.IsDir() {
  665. ext = path.Ext(info.Name())
  666. title = strings.TrimSuffix(info.Name(), ext)
  667. }
  668. id := ast.NewNodeID()
  669. curRelPath := filepath.ToSlash(strings.TrimPrefix(currentPath, localPath))
  670. targetPath := path.Join(baseTargetPath, id)
  671. hPath := path.Join(baseHPath, filepath.ToSlash(strings.TrimPrefix(currentPath, localPath)))
  672. hPath = strings.TrimSuffix(hPath, ext)
  673. if "" == curRelPath {
  674. curRelPath = "/"
  675. hPath = "/" + title
  676. } else {
  677. dirPath := targetPaths[path.Dir(curRelPath)]
  678. targetPath = path.Join(dirPath, id)
  679. }
  680. targetPath = strings.ReplaceAll(targetPath, ".sy/", "/")
  681. targetPath += ".sy"
  682. targetPaths[curRelPath] = targetPath
  683. if info.IsDir() {
  684. tree = treenode.NewTree(boxID, targetPath, hPath, title)
  685. importTrees = append(importTrees, tree)
  686. return nil
  687. }
  688. if !strings.HasSuffix(info.Name(), ".md") && !strings.HasSuffix(info.Name(), ".markdown") {
  689. return nil
  690. }
  691. data, readErr := os.ReadFile(currentPath)
  692. if nil != readErr {
  693. err = readErr
  694. return io.EOF
  695. }
  696. tree = parseStdMd(data)
  697. if nil == tree {
  698. logging.LogErrorf("parse tree [%s] failed", currentPath)
  699. return nil
  700. }
  701. tree.ID = id
  702. tree.Root.ID = id
  703. tree.Root.SetIALAttr("id", tree.Root.ID)
  704. tree.Root.SetIALAttr("title", title)
  705. tree.Box = boxID
  706. targetPath = path.Join(path.Dir(targetPath), tree.Root.ID+".sy")
  707. tree.Path = targetPath
  708. targetPaths[curRelPath] = targetPath
  709. tree.HPath = hPath
  710. tree.Root.Spec = "1"
  711. docDirLocalPath := filepath.Dir(filepath.Join(boxLocalPath, targetPath))
  712. assetDirPath := getAssetsDir(boxLocalPath, docDirLocalPath)
  713. currentDir := filepath.Dir(currentPath)
  714. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  715. if !entering || (ast.NodeLinkDest != n.Type && !n.IsTextMarkType("a")) {
  716. return ast.WalkContinue
  717. }
  718. var dest string
  719. if ast.NodeLinkDest == n.Type {
  720. dest = n.TokensStr()
  721. } else {
  722. dest = n.TextMarkAHref
  723. }
  724. if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
  725. processBase64Img(n, dest, assetDirPath, err)
  726. return ast.WalkContinue
  727. }
  728. dest = strings.ReplaceAll(dest, "%20", " ")
  729. dest = strings.ReplaceAll(dest, "%5C", "/")
  730. if ast.NodeLinkDest == n.Type {
  731. n.Tokens = []byte(dest)
  732. } else {
  733. n.TextMarkAHref = dest
  734. }
  735. if !util.IsRelativePath(dest) {
  736. return ast.WalkContinue
  737. }
  738. dest = filepath.ToSlash(dest)
  739. if "" == dest {
  740. return ast.WalkContinue
  741. }
  742. absDest := filepath.Join(currentDir, dest)
  743. fullPath, exist := assets[absDest]
  744. if !exist {
  745. absDest = filepath.Join(currentDir, string(html.DecodeDestination([]byte(dest))))
  746. }
  747. fullPath, exist = assets[absDest]
  748. if exist {
  749. existName := assetsDone[absDest]
  750. var name string
  751. if "" == existName {
  752. name = filepath.Base(fullPath)
  753. name = util.AssetName(name)
  754. assetTargetPath := filepath.Join(assetDirPath, name)
  755. if err = filelock.Copy(fullPath, assetTargetPath); nil != err {
  756. logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", fullPath, assetTargetPath, err)
  757. return ast.WalkContinue
  758. }
  759. assetsDone[absDest] = name
  760. } else {
  761. name = existName
  762. }
  763. if ast.NodeLinkDest == n.Type {
  764. n.Tokens = []byte("assets/" + name)
  765. } else {
  766. n.TextMarkAHref = "assets/" + name
  767. }
  768. }
  769. return ast.WalkContinue
  770. })
  771. reassignIDUpdated(tree)
  772. importTrees = append(importTrees, tree)
  773. return nil
  774. })
  775. } else { // 导入单个文件
  776. fileName := filepath.Base(localPath)
  777. if !strings.HasSuffix(fileName, ".md") && !strings.HasSuffix(fileName, ".markdown") {
  778. return errors.New(Conf.Language(79))
  779. }
  780. title := strings.TrimSuffix(fileName, ".markdown")
  781. title = strings.TrimSuffix(title, ".md")
  782. targetPath := strings.TrimSuffix(toPath, ".sy")
  783. id := ast.NewNodeID()
  784. targetPath = path.Join(targetPath, id+".sy")
  785. var data []byte
  786. data, err = os.ReadFile(localPath)
  787. if nil != err {
  788. return err
  789. }
  790. tree := parseStdMd(data)
  791. if nil == tree {
  792. msg := fmt.Sprintf("parse tree [%s] failed", localPath)
  793. logging.LogErrorf(msg)
  794. return errors.New(msg)
  795. }
  796. tree.ID = id
  797. tree.Root.ID = id
  798. tree.Root.SetIALAttr("id", tree.Root.ID)
  799. tree.Root.SetIALAttr("title", title)
  800. tree.Box = boxID
  801. tree.Path = targetPath
  802. tree.HPath = path.Join(baseHPath, title)
  803. tree.Root.Spec = "1"
  804. docDirLocalPath := filepath.Dir(filepath.Join(boxLocalPath, targetPath))
  805. assetDirPath := getAssetsDir(boxLocalPath, docDirLocalPath)
  806. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  807. if !entering || (ast.NodeLinkDest != n.Type && !n.IsTextMarkType("a")) {
  808. return ast.WalkContinue
  809. }
  810. var dest string
  811. if ast.NodeLinkDest == n.Type {
  812. dest = n.TokensStr()
  813. } else {
  814. dest = n.TextMarkAHref
  815. }
  816. if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
  817. processBase64Img(n, dest, assetDirPath, err)
  818. return ast.WalkContinue
  819. }
  820. dest = strings.ReplaceAll(dest, "%20", " ")
  821. dest = strings.ReplaceAll(dest, "%5C", "/")
  822. if ast.NodeLinkDest == n.Type {
  823. n.Tokens = []byte(dest)
  824. } else {
  825. n.TextMarkAHref = dest
  826. }
  827. if !util.IsRelativePath(dest) {
  828. return ast.WalkContinue
  829. }
  830. dest = filepath.ToSlash(dest)
  831. if "" == dest {
  832. return ast.WalkContinue
  833. }
  834. absolutePath := filepath.Join(filepath.Dir(localPath), dest)
  835. exist := gulu.File.IsExist(absolutePath)
  836. if !exist {
  837. absolutePath = filepath.Join(filepath.Dir(localPath), string(html.DecodeDestination([]byte(dest))))
  838. exist = gulu.File.IsExist(absolutePath)
  839. }
  840. if exist {
  841. name := filepath.Base(absolutePath)
  842. name = util.AssetName(name)
  843. assetTargetPath := filepath.Join(assetDirPath, name)
  844. if err = filelock.Copy(absolutePath, assetTargetPath); nil != err {
  845. logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", absolutePath, assetTargetPath, err)
  846. return ast.WalkContinue
  847. }
  848. if ast.NodeLinkDest == n.Type {
  849. n.Tokens = []byte("assets/" + name)
  850. } else {
  851. n.TextMarkAHref = "assets/" + name
  852. }
  853. }
  854. return ast.WalkContinue
  855. })
  856. reassignIDUpdated(tree)
  857. importTrees = append(importTrees, tree)
  858. }
  859. if 0 < len(importTrees) {
  860. initSearchLinks()
  861. convertWikiLinksAndTags()
  862. buildBlockRefInText()
  863. for i, tree := range importTrees {
  864. indexWriteTreeIndexQueue(tree)
  865. if 0 == i%4 {
  866. util.PushEndlessProgress(fmt.Sprintf(Conf.Language(66), fmt.Sprintf("%d/%d ", i, len(importTrees))+tree.HPath))
  867. }
  868. }
  869. util.PushClearProgress()
  870. importTrees = []*parse.Tree{}
  871. searchLinks = map[string]string{}
  872. }
  873. IncSync()
  874. debug.FreeOSMemory()
  875. return
  876. }
  877. func parseStdMd(markdown []byte) (ret *parse.Tree) {
  878. luteEngine := util.NewStdLute()
  879. ret = parse.Parse("", markdown, luteEngine.ParseOptions)
  880. if nil == ret {
  881. return
  882. }
  883. genTreeID(ret)
  884. imgHtmlBlock2InlineImg(ret)
  885. parse.NestedInlines2FlattedSpansHybrid(ret, false)
  886. return
  887. }
  888. func processBase64Img(n *ast.Node, dest string, assetDirPath string, err error) {
  889. base64TmpDir := filepath.Join(util.TempDir, "base64")
  890. os.MkdirAll(base64TmpDir, 0755)
  891. sep := strings.Index(dest, ";base64,")
  892. str := strings.TrimSpace(dest[sep+8:])
  893. re := regexp.MustCompile(`(?i)%0A`)
  894. str = re.ReplaceAllString(str, "\n")
  895. var decodeErr error
  896. unbased, decodeErr := base64.StdEncoding.DecodeString(str)
  897. if nil != decodeErr {
  898. logging.LogErrorf("decode base64 image failed: %s", decodeErr)
  899. return
  900. }
  901. dataReader := bytes.NewReader(unbased)
  902. var img image.Image
  903. var ext string
  904. typ := dest[5:sep]
  905. switch typ {
  906. case "image/png":
  907. img, decodeErr = png.Decode(dataReader)
  908. ext = ".png"
  909. case "image/jpeg":
  910. img, decodeErr = jpeg.Decode(dataReader)
  911. ext = ".jpg"
  912. default:
  913. logging.LogWarnf("unsupported base64 image type [%s]", typ)
  914. return
  915. }
  916. if nil != decodeErr {
  917. logging.LogErrorf("decode base64 image failed: %s", decodeErr)
  918. return
  919. }
  920. name := "image" + ext
  921. alt := n.Parent.ChildByType(ast.NodeLinkText)
  922. if nil != alt {
  923. name = alt.TokensStr() + ext
  924. }
  925. name = util.FilterUploadFileName(name)
  926. name = util.AssetName(name)
  927. tmp := filepath.Join(base64TmpDir, name)
  928. tmpFile, openErr := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE, 0644)
  929. if nil != openErr {
  930. logging.LogErrorf("open temp file [%s] failed: %s", tmp, openErr)
  931. return
  932. }
  933. var encodeErr error
  934. switch typ {
  935. case "image/png":
  936. encodeErr = png.Encode(tmpFile, img)
  937. case "image/jpeg":
  938. encodeErr = jpeg.Encode(tmpFile, img, &jpeg.Options{Quality: 100})
  939. }
  940. if nil != encodeErr {
  941. logging.LogErrorf("encode base64 image failed: %s", encodeErr)
  942. tmpFile.Close()
  943. return
  944. }
  945. tmpFile.Close()
  946. assetTargetPath := filepath.Join(assetDirPath, name)
  947. if err = filelock.Copy(tmp, assetTargetPath); nil != err {
  948. logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", tmp, assetTargetPath, err)
  949. return
  950. }
  951. n.Tokens = []byte("assets/" + name)
  952. }
  953. func imgHtmlBlock2InlineImg(tree *parse.Tree) {
  954. imgHtmlBlocks := map[*ast.Node]*html.Node{}
  955. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  956. if !entering {
  957. return ast.WalkContinue
  958. }
  959. if ast.NodeHTMLBlock == n.Type {
  960. htmlNodes, pErr := html.ParseFragment(bytes.NewReader(n.Tokens), &html.Node{Type: html.ElementNode})
  961. if nil != pErr {
  962. logging.LogErrorf("parse html block [%s] failed: %s", n.Tokens, pErr)
  963. return ast.WalkContinue
  964. }
  965. if 1 > len(htmlNodes) {
  966. return ast.WalkContinue
  967. }
  968. for _, htmlNode := range htmlNodes {
  969. if atom.Img == htmlNode.DataAtom {
  970. imgHtmlBlocks[n] = htmlNode
  971. break
  972. }
  973. }
  974. }
  975. return ast.WalkContinue
  976. })
  977. for n, htmlImg := range imgHtmlBlocks {
  978. src := domAttrValue(htmlImg, "src")
  979. alt := domAttrValue(htmlImg, "alt")
  980. title := domAttrValue(htmlImg, "title")
  981. p := &ast.Node{Type: ast.NodeParagraph, ID: n.ID}
  982. img := &ast.Node{Type: ast.NodeImage}
  983. p.AppendChild(img)
  984. img.AppendChild(&ast.Node{Type: ast.NodeBang})
  985. img.AppendChild(&ast.Node{Type: ast.NodeOpenBracket})
  986. img.AppendChild(&ast.Node{Type: ast.NodeLinkText, Tokens: []byte(alt)})
  987. img.AppendChild(&ast.Node{Type: ast.NodeCloseBracket})
  988. img.AppendChild(&ast.Node{Type: ast.NodeOpenParen})
  989. img.AppendChild(&ast.Node{Type: ast.NodeLinkDest, Tokens: []byte(src)})
  990. if "" != title {
  991. img.AppendChild(&ast.Node{Type: ast.NodeLinkSpace})
  992. img.AppendChild(&ast.Node{Type: ast.NodeLinkTitle})
  993. }
  994. img.AppendChild(&ast.Node{Type: ast.NodeCloseParen})
  995. n.InsertBefore(p)
  996. n.Unlink()
  997. }
  998. return
  999. }
  1000. func reassignIDUpdated(tree *parse.Tree) {
  1001. var blockCount int
  1002. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  1003. if !entering || "" == n.ID {
  1004. return ast.WalkContinue
  1005. }
  1006. blockCount++
  1007. return ast.WalkContinue
  1008. })
  1009. ids := make([]string, blockCount)
  1010. min, _ := strconv.ParseInt(time.Now().Add(-1*time.Duration(blockCount)*time.Second).Format("20060102150405"), 10, 64)
  1011. for i := 0; i < blockCount; i++ {
  1012. ids[i] = newID(fmt.Sprintf("%d", min))
  1013. min++
  1014. }
  1015. var i int
  1016. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  1017. if !entering || "" == n.ID {
  1018. return ast.WalkContinue
  1019. }
  1020. n.ID = ids[i]
  1021. n.SetIALAttr("id", n.ID)
  1022. n.SetIALAttr("updated", util.TimeFromID(n.ID))
  1023. i++
  1024. return ast.WalkContinue
  1025. })
  1026. tree.ID = tree.Root.ID
  1027. tree.Path = path.Join(path.Dir(tree.Path), tree.ID+".sy")
  1028. tree.Root.SetIALAttr("id", tree.Root.ID)
  1029. }
  1030. func newID(t string) string {
  1031. return t + "-" + randStr(7)
  1032. }
  1033. func randStr(length int) string {
  1034. letter := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
  1035. b := make([]rune, length)
  1036. for i := range b {
  1037. b[i] = letter[rand.Intn(len(letter))]
  1038. }
  1039. return string(b)
  1040. }
  1041. func domAttrValue(n *html.Node, attrName string) string {
  1042. if nil == n {
  1043. return ""
  1044. }
  1045. for _, attr := range n.Attr {
  1046. if attr.Key == attrName {
  1047. return attr.Val
  1048. }
  1049. }
  1050. return ""
  1051. }
  1052. var importTrees []*parse.Tree
  1053. var searchLinks = map[string]string{}
  1054. func initSearchLinks() {
  1055. for _, tree := range importTrees {
  1056. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  1057. if !entering || (ast.NodeDocument != n.Type && ast.NodeHeading != n.Type) {
  1058. return ast.WalkContinue
  1059. }
  1060. nodePath := tree.HPath + "#"
  1061. if ast.NodeHeading == n.Type {
  1062. nodePath += n.Text()
  1063. }
  1064. searchLinks[nodePath] = n.ID
  1065. return ast.WalkContinue
  1066. })
  1067. }
  1068. }
  1069. func convertWikiLinksAndTags() {
  1070. for _, tree := range importTrees {
  1071. convertWikiLinksAndTags0(tree)
  1072. }
  1073. }
  1074. func convertWikiLinksAndTags0(tree *parse.Tree) {
  1075. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  1076. if !entering || ast.NodeText != n.Type {
  1077. return ast.WalkContinue
  1078. }
  1079. text := n.TokensStr()
  1080. length := len(text)
  1081. start, end := 0, length
  1082. for {
  1083. part := text[start:end]
  1084. if idx := strings.Index(part, "]]"); 0 > idx {
  1085. break
  1086. } else {
  1087. end = start + idx
  1088. }
  1089. if idx := strings.Index(part, "[["); 0 > idx {
  1090. break
  1091. } else {
  1092. start += idx
  1093. }
  1094. if end <= start {
  1095. break
  1096. }
  1097. link := path.Join(path.Dir(tree.HPath), text[start+2:end]) // 统一转为绝对路径方便后续查找
  1098. linkText := path.Base(link)
  1099. dynamicAnchorText := true
  1100. if linkParts := strings.Split(link, "|"); 1 < len(linkParts) {
  1101. link = linkParts[0]
  1102. linkText = linkParts[1]
  1103. dynamicAnchorText = false
  1104. }
  1105. link, linkText = strings.TrimSpace(link), strings.TrimSpace(linkText)
  1106. if !strings.Contains(link, "#") {
  1107. link += "#" // 在结尾统一带上锚点方便后续查找
  1108. }
  1109. id := searchLinkID(link)
  1110. if "" == id {
  1111. start, end = end, length
  1112. continue
  1113. }
  1114. linkText = strings.TrimPrefix(linkText, "/")
  1115. repl := "((" + id + " '" + linkText + "'))"
  1116. if !dynamicAnchorText {
  1117. repl = "((" + id + " \"" + linkText + "\"))"
  1118. }
  1119. end += 2
  1120. text = text[:start] + repl + text[end:]
  1121. start, end = start+len(repl), len(text)
  1122. length = end
  1123. }
  1124. text = convertTags(text) // 导入标签语法
  1125. n.Tokens = gulu.Str.ToBytes(text)
  1126. return ast.WalkContinue
  1127. })
  1128. }
  1129. func convertTags(text string) (ret string) {
  1130. if !util.MarkdownSettings.InlineTag {
  1131. return text
  1132. }
  1133. pos, i := -1, 0
  1134. tokens := []byte(text)
  1135. for ; i < len(tokens); i++ {
  1136. if '#' == tokens[i] && (0 == i || ' ' == tokens[i-1] || (-1 < pos && '#' == tokens[pos])) {
  1137. if i < len(tokens)-1 && '#' == tokens[i+1] {
  1138. pos = -1
  1139. continue
  1140. }
  1141. pos = i
  1142. continue
  1143. }
  1144. if -1 < pos && ' ' == tokens[i] {
  1145. tokens = append(tokens, 0)
  1146. copy(tokens[i+1:], tokens[i:])
  1147. tokens[i] = '#'
  1148. pos = -1
  1149. i++
  1150. }
  1151. }
  1152. if -1 < pos && pos < i {
  1153. tokens = append(tokens, '#')
  1154. }
  1155. return string(tokens)
  1156. }
  1157. // buildBlockRefInText 将文本节点进行结构化处理。
  1158. func buildBlockRefInText() {
  1159. lute := NewLute()
  1160. lute.SetHTMLTag2TextMark(true)
  1161. for _, tree := range importTrees {
  1162. tree.MergeText()
  1163. var unlinkTextNodes []*ast.Node
  1164. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  1165. if !entering || ast.NodeText != n.Type {
  1166. return ast.WalkContinue
  1167. }
  1168. if nil == n.Tokens {
  1169. return ast.WalkContinue
  1170. }
  1171. t := parse.Inline("", n.Tokens, lute.ParseOptions) // 使用行级解析
  1172. parse.NestedInlines2FlattedSpans(t, false)
  1173. var children []*ast.Node
  1174. for c := t.Root.FirstChild.FirstChild; nil != c; c = c.Next {
  1175. children = append(children, c)
  1176. }
  1177. for _, c := range children {
  1178. n.InsertBefore(c)
  1179. }
  1180. unlinkTextNodes = append(unlinkTextNodes, n)
  1181. return ast.WalkContinue
  1182. })
  1183. for _, node := range unlinkTextNodes {
  1184. node.Unlink()
  1185. }
  1186. }
  1187. }
  1188. func searchLinkID(link string) (id string) {
  1189. id = searchLinks[link]
  1190. if "" != id {
  1191. return
  1192. }
  1193. baseName := path.Base(link)
  1194. for searchLink, searchID := range searchLinks {
  1195. if path.Base(searchLink) == baseName {
  1196. return searchID
  1197. }
  1198. }
  1199. return
  1200. }