import.go 36 KB

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