database.go 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. // SiYuan - Build Your Eternal Digital Garden
  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 sql
  17. import (
  18. "bytes"
  19. "database/sql"
  20. "errors"
  21. "os"
  22. "path/filepath"
  23. "regexp"
  24. "strings"
  25. "time"
  26. "unicode/utf8"
  27. "github.com/88250/gulu"
  28. "github.com/88250/lute/ast"
  29. "github.com/88250/lute/html"
  30. "github.com/88250/lute/parse"
  31. "github.com/mattn/go-sqlite3"
  32. _ "github.com/mattn/go-sqlite3"
  33. "github.com/siyuan-note/logging"
  34. "github.com/siyuan-note/siyuan/kernel/treenode"
  35. "github.com/siyuan-note/siyuan/kernel/util"
  36. )
  37. var (
  38. db *sql.DB
  39. historyDB *sql.DB
  40. )
  41. func init() {
  42. regex := func(re, s string) (bool, error) {
  43. re = strings.ReplaceAll(re, "\\\\", "\\")
  44. return regexp.MatchString(re, s)
  45. }
  46. sql.Register("sqlite3_extended", &sqlite3.SQLiteDriver{
  47. ConnectHook: func(conn *sqlite3.SQLiteConn) error {
  48. return conn.RegisterFunc("regexp", regex, true)
  49. },
  50. })
  51. }
  52. func InitDatabase(forceRebuild bool) (err error) {
  53. util.IncBootProgress(2, "Initializing database...")
  54. if forceRebuild {
  55. WaitForWritingDatabase()
  56. }
  57. initDBConnection()
  58. if !forceRebuild {
  59. // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建
  60. if util.DatabaseVer == getDatabaseVer() {
  61. return
  62. }
  63. logging.LogInfof("the database structure is changed, rebuilding database...")
  64. }
  65. // 不存在库或者版本不一致都会走到这里
  66. db.Close()
  67. if gulu.File.IsExist(util.DBPath) {
  68. if err = removeDatabaseFile(); nil != err {
  69. logging.LogErrorf("remove database file [%s] failed: %s", util.DBPath, err)
  70. util.PushClearProgress()
  71. return
  72. }
  73. }
  74. if gulu.File.IsExist(util.BlockTreePath) {
  75. os.RemoveAll(util.BlockTreePath)
  76. }
  77. initDBConnection()
  78. initDBTables()
  79. logging.LogInfof("reinitialized database [%s]", util.DBPath)
  80. return
  81. }
  82. func initDBTables() {
  83. db.Exec("DROP TABLE stat")
  84. _, err := db.Exec("CREATE TABLE stat (key, value)")
  85. if nil != err {
  86. logging.LogFatalf("create table [stat] failed: %s", err)
  87. }
  88. setDatabaseVer()
  89. db.Exec("DROP TABLE blocks")
  90. _, err = db.Exec("CREATE TABLE blocks (id, parent_id, root_id, hash, box, path, hpath, name, alias, memo, tag, content, fcontent, markdown, length, type, subtype, ial, sort, created, updated)")
  91. if nil != err {
  92. logging.LogFatalf("create table [blocks] failed: %s", err)
  93. }
  94. db.Exec("DROP TABLE blocks_fts")
  95. _, err = db.Exec("CREATE VIRTUAL TABLE blocks_fts USING fts5(id UNINDEXED, parent_id UNINDEXED, root_id UNINDEXED, hash UNINDEXED, box UNINDEXED, path UNINDEXED, hpath, name, alias, memo, tag, content, fcontent, markdown UNINDEXED, length UNINDEXED, type UNINDEXED, subtype UNINDEXED, ial, sort UNINDEXED, created UNINDEXED, updated UNINDEXED, tokenize=\"siyuan\")")
  96. if nil != err {
  97. logging.LogFatalf("create table [blocks_fts] failed: %s", err)
  98. }
  99. db.Exec("DROP TABLE blocks_fts_case_insensitive")
  100. _, err = db.Exec("CREATE VIRTUAL TABLE blocks_fts_case_insensitive USING fts5(id UNINDEXED, parent_id UNINDEXED, root_id UNINDEXED, hash UNINDEXED, box UNINDEXED, path UNINDEXED, hpath, name, alias, memo, tag, content, fcontent, markdown UNINDEXED, length UNINDEXED, type UNINDEXED, subtype UNINDEXED, ial, sort UNINDEXED, created UNINDEXED, updated UNINDEXED, tokenize=\"siyuan case_insensitive\")")
  101. if nil != err {
  102. logging.LogFatalf("create table [blocks_fts_case_insensitive] failed: %s", err)
  103. }
  104. db.Exec("DROP TABLE spans")
  105. _, err = db.Exec("CREATE TABLE spans (id, block_id, root_id, box, path, content, markdown, type, ial)")
  106. if nil != err {
  107. logging.LogFatalf("create table [spans] failed: %s", err)
  108. }
  109. db.Exec("DROP TABLE assets")
  110. _, err = db.Exec("CREATE TABLE assets (id, block_id, root_id, box, docpath, path, name, title, hash)")
  111. if nil != err {
  112. logging.LogFatalf("create table [assets] failed: %s", err)
  113. }
  114. db.Exec("DROP TABLE attributes")
  115. _, err = db.Exec("CREATE TABLE attributes (id, name, value, type, block_id, root_id, box, path)")
  116. if nil != err {
  117. logging.LogFatalf("create table [attributes] failed: %s", err)
  118. }
  119. db.Exec("DROP TABLE refs")
  120. _, err = db.Exec("CREATE TABLE refs (id, def_block_id, def_block_parent_id, def_block_root_id, def_block_path, block_id, root_id, box, path, content, markdown, type)")
  121. if nil != err {
  122. logging.LogFatalf("create table [refs] failed: %s", err)
  123. }
  124. db.Exec("DROP TABLE file_annotation_refs")
  125. _, err = db.Exec("CREATE TABLE file_annotation_refs (id, file_path, annotation_id, block_id, root_id, box, path, content, type)")
  126. if nil != err {
  127. logging.LogFatalf("create table [refs] failed: %s", err)
  128. }
  129. }
  130. func InitHistoryDatabase(forceRebuild bool) {
  131. initHistoryDBConnection()
  132. if !forceRebuild && gulu.File.IsExist(util.HistoryDBPath) {
  133. return
  134. }
  135. historyDB.Close()
  136. if err := os.RemoveAll(util.HistoryDBPath); nil != err {
  137. logging.LogErrorf("remove history database file [%s] failed: %s", util.HistoryDBPath, err)
  138. return
  139. }
  140. initHistoryDBConnection()
  141. initHistoryDBTables()
  142. }
  143. func initHistoryDBConnection() {
  144. if nil != historyDB {
  145. historyDB.Close()
  146. }
  147. dsn := util.HistoryDBPath + "?_journal_mode=OFF" +
  148. "&_synchronous=OFF" +
  149. "&_secure_delete=OFF" +
  150. "&_cache_size=-20480" +
  151. "&_page_size=8192" +
  152. "&_busy_timeout=7000" +
  153. "&_ignore_check_constraints=ON" +
  154. "&_temp_store=MEMORY" +
  155. "&_case_sensitive_like=OFF" +
  156. "&_locking_mode=EXCLUSIVE"
  157. var err error
  158. historyDB, err = sql.Open("sqlite3_extended", dsn)
  159. if nil != err {
  160. logging.LogFatalf("create database failed: %s", err)
  161. }
  162. historyDB.SetMaxIdleConns(1)
  163. historyDB.SetMaxOpenConns(1)
  164. historyDB.SetConnMaxLifetime(365 * 24 * time.Hour)
  165. }
  166. func initHistoryDBTables() {
  167. historyDB.Exec("DROP TABLE histories_fts_case_insensitive")
  168. _, err := historyDB.Exec("CREATE VIRTUAL TABLE histories_fts_case_insensitive USING fts5(type UNINDEXED, op UNINDEXED, title, content, path UNINDEXED, created UNINDEXED, tokenize=\"siyuan case_insensitive\")")
  169. if nil != err {
  170. logging.LogFatalf("create table [histories_fts_case_insensitive] failed: %s", err)
  171. }
  172. }
  173. func IndexMode() {
  174. if nil != db {
  175. db.Close()
  176. }
  177. dsn := util.DBPath + "?_journal_mode=OFF" +
  178. "&_synchronous=OFF" +
  179. "&_secure_delete=OFF" +
  180. "&_cache_size=-20480" +
  181. "&_page_size=8192" +
  182. "&_busy_timeout=7000" +
  183. "&_ignore_check_constraints=ON" +
  184. "&_temp_store=MEMORY" +
  185. "&_case_sensitive_like=OFF" +
  186. "&_locking_mode=EXCLUSIVE"
  187. var err error
  188. db, err = sql.Open("sqlite3_extended", dsn)
  189. if nil != err {
  190. logging.LogFatalf("create database failed: %s", err)
  191. }
  192. db.SetMaxIdleConns(1)
  193. db.SetMaxOpenConns(1)
  194. db.SetConnMaxLifetime(365 * 24 * time.Hour)
  195. }
  196. func NormalMode() {
  197. initDBConnection()
  198. }
  199. func initDBConnection() {
  200. if nil != db {
  201. db.Close()
  202. }
  203. dsn := util.DBPath + "?_journal_mode=WAL" +
  204. "&_synchronous=OFF" +
  205. "&_secure_delete=OFF" +
  206. "&_cache_size=-20480" +
  207. "&_page_size=8192" +
  208. "&_busy_timeout=7000" +
  209. "&_ignore_check_constraints=ON" +
  210. "&_temp_store=MEMORY" +
  211. "&_case_sensitive_like=OFF"
  212. var err error
  213. db, err = sql.Open("sqlite3_extended", dsn)
  214. if nil != err {
  215. logging.LogFatalf("create database failed: %s", err)
  216. }
  217. db.SetMaxIdleConns(20)
  218. db.SetMaxOpenConns(20)
  219. db.SetConnMaxLifetime(365 * 24 * time.Hour)
  220. }
  221. func SetCaseSensitive(b bool) {
  222. if b {
  223. db.Exec("PRAGMA case_sensitive_like = ON;")
  224. } else {
  225. db.Exec("PRAGMA case_sensitive_like = OFF;")
  226. }
  227. }
  228. func refsFromTree(tree *parse.Tree) (refs []*Ref, fileAnnotationRefs []*FileAnnotationRef) {
  229. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  230. if entering {
  231. return ast.WalkContinue
  232. }
  233. if ast.NodeBlockRefID == n.Type {
  234. ref := buildRef(tree, n)
  235. refs = append(refs, ref)
  236. } else if ast.NodeFileAnnotationRefID == n.Type {
  237. pathID := n.TokensStr()
  238. idx := strings.LastIndex(pathID, "/")
  239. if -1 == idx {
  240. return ast.WalkContinue
  241. }
  242. filePath := pathID[:idx]
  243. annotationID := pathID[idx+1:]
  244. anchor := n.Parent.ChildByType(ast.NodeFileAnnotationRefText)
  245. text := filePath
  246. if nil != anchor {
  247. text = anchor.Text()
  248. }
  249. parentBlock := treenode.ParentBlock(n)
  250. ref := &FileAnnotationRef{
  251. ID: ast.NewNodeID(),
  252. FilePath: filePath,
  253. AnnotationID: annotationID,
  254. BlockID: parentBlock.ID,
  255. RootID: tree.ID,
  256. Box: tree.Box,
  257. Path: tree.Path,
  258. Content: text,
  259. Type: treenode.TypeAbbr(n.Type.String()),
  260. }
  261. fileAnnotationRefs = append(fileAnnotationRefs, ref)
  262. }
  263. return ast.WalkContinue
  264. })
  265. return
  266. }
  267. func buildRef(tree *parse.Tree, refIDNode *ast.Node) *Ref {
  268. markdown := treenode.FormatNode(refIDNode.Parent, luteEngine)
  269. defBlockID := refIDNode.TokensStr()
  270. var defBlockParentID, defBlockRootID, defBlockPath string
  271. defBlock := treenode.GetBlockTree(defBlockID)
  272. if nil != defBlock {
  273. defBlockParentID = defBlock.ParentID
  274. defBlockRootID = defBlock.RootID
  275. defBlockPath = defBlock.Path
  276. }
  277. text := treenode.GetDynamicBlockRefText(refIDNode.Parent)
  278. parentBlock := treenode.ParentBlock(refIDNode)
  279. return &Ref{
  280. ID: ast.NewNodeID(),
  281. DefBlockID: defBlockID,
  282. DefBlockParentID: defBlockParentID,
  283. DefBlockRootID: defBlockRootID,
  284. DefBlockPath: defBlockPath,
  285. BlockID: parentBlock.ID,
  286. RootID: tree.ID,
  287. Box: tree.Box,
  288. Path: tree.Path,
  289. Content: text,
  290. Markdown: markdown,
  291. Type: treenode.TypeAbbr(refIDNode.Type.String()),
  292. }
  293. }
  294. func ResolveRefContent(block *Block, anchors *map[string]string) (ret string) {
  295. if "d" == block.Type {
  296. (*anchors)[block.ID] = block.Content
  297. return block.Content
  298. }
  299. tree := parse.Parse("", []byte(block.Markdown), luteEngine.ParseOptions)
  300. depth := 0
  301. var stack []string
  302. c := treenode.FirstLeafBlock(tree.Root)
  303. ret = resolveRefContent0(c, anchors, &depth, &stack)
  304. return
  305. }
  306. func resolveRefContent0(node *ast.Node, anchors *map[string]string, depth *int, stack *[]string) (ret string) {
  307. *depth++
  308. if 7 < *depth {
  309. return ""
  310. }
  311. if ast.NodeBlockRefID == node.Type {
  312. id := node.TokensStr()
  313. var ok bool
  314. if ret, ok = (*anchors)[id]; ok {
  315. return ret
  316. }
  317. if gulu.Str.Contains(id, *stack) {
  318. return ""
  319. }
  320. defBlock := GetBlock(id)
  321. if nil == defBlock {
  322. return "block not found"
  323. }
  324. if "" != defBlock.Name {
  325. (*anchors)[id] = defBlock.Name
  326. return defBlock.Name
  327. }
  328. if "d" == defBlock.Type {
  329. (*anchors)[id] = defBlock.Content
  330. return defBlock.Content
  331. }
  332. tree := parse.Parse("", gulu.Str.ToBytes(defBlock.Markdown), luteEngine.ParseOptions)
  333. c := treenode.FirstLeafBlock(tree.Root)
  334. *stack = append(*stack, id)
  335. ret = resolveRefContent0(c, anchors, depth, stack)
  336. (*anchors)[id] = ret
  337. return
  338. }
  339. buf := &bytes.Buffer{}
  340. buf.Grow(4096)
  341. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  342. if !entering {
  343. return ast.WalkContinue
  344. }
  345. switch n.Type {
  346. case ast.NodeDocument:
  347. buf.WriteString(n.IALAttr("title"))
  348. return ast.WalkStop
  349. case ast.NodeTagOpenMarker, ast.NodeTagCloseMarker:
  350. buf.WriteByte('#')
  351. case ast.NodeText, ast.NodeLinkText, ast.NodeLinkTitle, ast.NodeFileAnnotationRefText, ast.NodeFootnotesRef,
  352. ast.NodeCodeSpanContent, ast.NodeInlineMathContent, ast.NodeCodeBlockCode, ast.NodeMathBlockContent:
  353. buf.Write(n.Tokens)
  354. case ast.NodeTextMark:
  355. buf.WriteString(n.TextMarkTextContent)
  356. case ast.NodeBlockRef:
  357. if anchor := n.ChildByType(ast.NodeBlockRefText); nil != anchor {
  358. buf.WriteString(anchor.Text())
  359. return ast.WalkSkipChildren
  360. } else if anchor = n.ChildByType(ast.NodeBlockRefDynamicText); nil != anchor {
  361. buf.WriteString(anchor.Text())
  362. return ast.WalkSkipChildren
  363. }
  364. defID := n.ChildByType(ast.NodeBlockRefID)
  365. anchor := resolveRefContent0(defID, anchors, depth, stack)
  366. (*anchors)[defID.TokensStr()] = anchor
  367. buf.WriteString(anchor)
  368. }
  369. return ast.WalkContinue
  370. })
  371. return buf.String()
  372. }
  373. func fromTree(node *ast.Node, tree *parse.Tree) (blocks []*Block, spans []*Span, assets []*Asset, attributes []*Attribute) {
  374. rootID := tree.Root.ID
  375. boxID := tree.Box
  376. p := tree.Path
  377. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  378. if !entering {
  379. return ast.WalkContinue
  380. }
  381. // 构造行级元素
  382. spanBlocks, spanSpans, spanAssets, spanAttrs, walkStatus := buildSpanFromNode(n, tree, rootID, boxID, p)
  383. if 0 < len(spanBlocks) {
  384. blocks = append(blocks, spanBlocks...)
  385. }
  386. if 0 < len(spanSpans) {
  387. spans = append(spans, spanSpans...)
  388. }
  389. if 0 < len(spanAssets) {
  390. assets = append(assets, spanAssets...)
  391. }
  392. if 0 < len(spanAttrs) {
  393. attributes = append(attributes, spanAttrs...)
  394. }
  395. // 构造属性
  396. attrs := buildAttributeFromNode(n, rootID, boxID, p)
  397. if 0 < len(attrs) {
  398. attributes = append(attributes, attrs...)
  399. }
  400. if -1 != walkStatus {
  401. return walkStatus
  402. }
  403. // 构造块级元素
  404. if "" == n.ID || !n.IsBlock() {
  405. return ast.WalkContinue
  406. }
  407. b, attrs := buildBlockFromNode(n, tree)
  408. blocks = append(blocks, b)
  409. if 0 < len(attrs) {
  410. attributes = append(attributes, attrs...)
  411. }
  412. return ast.WalkContinue
  413. })
  414. return
  415. }
  416. func buildAttributeFromNode(n *ast.Node, rootID, boxID, p string) (attributes []*Attribute) {
  417. switch n.Type {
  418. case ast.NodeKramdownSpanIAL:
  419. parentBlock := treenode.ParentBlock(n)
  420. attrs := parse.IALValMap(n)
  421. for name, val := range attrs {
  422. if !isAttr(name) {
  423. continue
  424. }
  425. attr := &Attribute{
  426. ID: ast.NewNodeID(),
  427. Name: name,
  428. Value: val,
  429. Type: "s",
  430. BlockID: parentBlock.ID,
  431. RootID: rootID,
  432. Box: boxID,
  433. Path: p,
  434. }
  435. attributes = append(attributes, attr)
  436. }
  437. case ast.NodeKramdownBlockIAL:
  438. attrs := parse.IALValMap(n)
  439. for name, val := range attrs {
  440. if !isAttr(name) {
  441. continue
  442. }
  443. attr := &Attribute{
  444. ID: ast.NewNodeID(),
  445. Name: name,
  446. Value: val,
  447. Type: "b",
  448. BlockID: n.ID,
  449. RootID: rootID,
  450. Box: boxID,
  451. Path: p,
  452. }
  453. attributes = append(attributes, attr)
  454. }
  455. }
  456. return
  457. }
  458. func isAttr(name string) bool {
  459. return strings.HasPrefix(name, "custom-") || "name" == name || "alias" == name || "memo" == name || "bookmark" == name || "fold" == name || "heading-fold" == name || "style" == name
  460. }
  461. func buildSpanFromNode(n *ast.Node, tree *parse.Tree, rootID, boxID, p string) (blocks []*Block, spans []*Span, assets []*Asset, attributes []*Attribute, walkStatus ast.WalkStatus) {
  462. boxLocalPath := filepath.Join(util.DataDir, boxID)
  463. docDirLocalPath := filepath.Join(boxLocalPath, p)
  464. switch n.Type {
  465. case ast.NodeLinkText:
  466. text := n.Text()
  467. markdown := treenode.FormatNode(n.Parent, luteEngine)
  468. parentBlock := treenode.ParentBlock(n)
  469. span := &Span{
  470. ID: ast.NewNodeID(),
  471. BlockID: parentBlock.ID,
  472. RootID: rootID,
  473. Box: boxID,
  474. Path: p,
  475. Content: text,
  476. Markdown: markdown,
  477. Type: treenode.TypeAbbr(n.Type.String()),
  478. IAL: treenode.IALStr(n),
  479. }
  480. spans = append(spans, span)
  481. walkStatus = ast.WalkContinue
  482. return
  483. case ast.NodeTag, ast.NodeInlineMath, ast.NodeCodeSpan, ast.NodeEmphasis, ast.NodeStrong, ast.NodeStrikethrough, ast.NodeMark, ast.NodeSup, ast.NodeSub, ast.NodeKbd, ast.NodeUnderline, ast.NodeTextMark:
  484. typ := treenode.TypeAbbr(n.Type.String())
  485. var text string
  486. switch n.Type {
  487. case ast.NodeTag, ast.NodeEmphasis, ast.NodeStrong, ast.NodeStrikethrough, ast.NodeMark, ast.NodeSup, ast.NodeSub, ast.NodeKbd, ast.NodeUnderline:
  488. text = n.Text()
  489. case ast.NodeInlineMath:
  490. text = n.ChildByType(ast.NodeInlineMathContent).TokensStr()
  491. case ast.NodeCodeSpan:
  492. text = n.ChildByType(ast.NodeCodeSpanContent).TokensStr()
  493. case ast.NodeTextMark:
  494. text = n.TextMarkTextContent
  495. typ = typ + " " + n.TextMarkType
  496. }
  497. markdown := treenode.FormatNode(n, luteEngine)
  498. parentBlock := treenode.ParentBlock(n)
  499. span := &Span{
  500. ID: ast.NewNodeID(),
  501. BlockID: parentBlock.ID,
  502. RootID: rootID,
  503. Box: boxID,
  504. Path: p,
  505. Content: text,
  506. Markdown: markdown,
  507. Type: typ,
  508. IAL: treenode.IALStr(n),
  509. }
  510. spans = append(spans, span)
  511. walkStatus = ast.WalkSkipChildren
  512. return
  513. case ast.NodeLinkDest:
  514. text := n.TokensStr()
  515. markdown := treenode.FormatNode(n.Parent, luteEngine)
  516. parentBlock := treenode.ParentBlock(n)
  517. span := &Span{
  518. ID: ast.NewNodeID(),
  519. BlockID: parentBlock.ID,
  520. RootID: rootID,
  521. Box: boxID,
  522. Path: p,
  523. Content: text,
  524. Markdown: markdown,
  525. Type: treenode.TypeAbbr(n.Type.String()),
  526. IAL: treenode.IALStr(n),
  527. }
  528. spans = append(spans, span)
  529. // assetsLinkDestsInTree
  530. if !IsAssetLinkDest(n.Tokens) {
  531. walkStatus = ast.WalkContinue
  532. return
  533. }
  534. dest := gulu.Str.FromBytes(n.Tokens)
  535. parentBlock = treenode.ParentBlock(n)
  536. var title string
  537. if titleNode := n.Parent.ChildByType(ast.NodeLinkTitle); nil != titleNode {
  538. title = gulu.Str.FromBytes(titleNode.Tokens)
  539. }
  540. var hash string
  541. var hashErr error
  542. if lp := assetLocalPath(dest, boxLocalPath, docDirLocalPath); "" != lp {
  543. if !gulu.File.IsDir(lp) {
  544. hash, hashErr = util.GetEtag(lp)
  545. if nil != hashErr {
  546. logging.LogErrorf("calc asset [%s] hash failed: %s", lp, hashErr)
  547. }
  548. }
  549. }
  550. name, _ := util.LastID(dest)
  551. asset := &Asset{
  552. ID: ast.NewNodeID(),
  553. BlockID: parentBlock.ID,
  554. RootID: rootID,
  555. Box: boxID,
  556. DocPath: p,
  557. Path: dest,
  558. Name: name,
  559. Title: title,
  560. Hash: hash,
  561. }
  562. assets = append(assets, asset)
  563. walkStatus = ast.WalkSkipChildren
  564. return
  565. case ast.NodeDocument:
  566. if asset := docTitleImgAsset(n); nil != asset {
  567. assets = append(assets, asset)
  568. }
  569. if tags := docTagSpans(n); 0 < len(tags) {
  570. spans = append(spans, tags...)
  571. }
  572. case ast.NodeInlineHTML, ast.NodeHTMLBlock, ast.NodeIFrame, ast.NodeWidget, ast.NodeAudio, ast.NodeVideo:
  573. nodes, err := html.ParseFragment(bytes.NewReader(n.Tokens), &html.Node{Type: html.ElementNode})
  574. if nil != err {
  575. logging.LogErrorf("parse HTML failed: %s", err)
  576. walkStatus = ast.WalkContinue
  577. return
  578. }
  579. if 1 > len(nodes) &&
  580. ast.NodeHTMLBlock != n.Type { // HTML 块若内容为空时无法在数据库中查询到 https://github.com/siyuan-note/siyuan/issues/4691
  581. walkStatus = ast.WalkContinue
  582. return
  583. }
  584. if ast.NodeHTMLBlock == n.Type || ast.NodeIFrame == n.Type || ast.NodeWidget == n.Type || ast.NodeAudio == n.Type || ast.NodeVideo == n.Type {
  585. b, attrs := buildBlockFromNode(n, tree)
  586. blocks = append(blocks, b)
  587. attributes = append(attributes, attrs...)
  588. }
  589. if 1 > len(nodes) {
  590. walkStatus = ast.WalkContinue
  591. return
  592. }
  593. var src []byte
  594. for _, attr := range nodes[0].Attr {
  595. if "src" == attr.Key || "data-assets" == attr.Key || "custom-data-assets" == attr.Key {
  596. src = gulu.Str.ToBytes(attr.Val)
  597. break
  598. }
  599. }
  600. if 1 > len(src) {
  601. walkStatus = ast.WalkContinue
  602. return
  603. }
  604. if !IsAssetLinkDest(src) {
  605. walkStatus = ast.WalkContinue
  606. return
  607. }
  608. dest := string(src)
  609. var hash string
  610. var hashErr error
  611. if lp := assetLocalPath(dest, boxLocalPath, docDirLocalPath); "" != lp {
  612. hash, hashErr = util.GetEtag(lp)
  613. if nil != hashErr {
  614. logging.LogErrorf("calc asset [%s] hash failed: %s", lp, hashErr)
  615. }
  616. }
  617. parentBlock := treenode.ParentBlock(n)
  618. if ast.NodeInlineHTML != n.Type {
  619. parentBlock = n
  620. }
  621. name, _ := util.LastID(dest)
  622. asset := &Asset{
  623. ID: ast.NewNodeID(),
  624. BlockID: parentBlock.ID,
  625. RootID: rootID,
  626. Box: boxID,
  627. DocPath: p,
  628. Path: dest,
  629. Name: name,
  630. Title: "",
  631. Hash: hash,
  632. }
  633. assets = append(assets, asset)
  634. walkStatus = ast.WalkSkipChildren
  635. return
  636. }
  637. walkStatus = -1
  638. return
  639. }
  640. func BuildBlockFromNode(n *ast.Node, tree *parse.Tree) (block *Block) {
  641. block, _ = buildBlockFromNode(n, tree)
  642. return
  643. }
  644. func buildBlockFromNode(n *ast.Node, tree *parse.Tree) (block *Block, attributes []*Attribute) {
  645. boxID := tree.Box
  646. p := tree.Path
  647. rootID := tree.Root.ID
  648. name := html.UnescapeString(n.IALAttr("name"))
  649. alias := html.UnescapeString(n.IALAttr("alias"))
  650. memo := html.UnescapeString(n.IALAttr("memo"))
  651. tag := tagFromNode(n)
  652. var content, fcontent, markdown, parentID string
  653. ialContent := treenode.IALStr(n)
  654. hash := treenode.NodeHash(n, tree, luteEngine)
  655. var length int
  656. if ast.NodeDocument == n.Type {
  657. content = n.IALAttr("title")
  658. fcontent = content
  659. length = utf8.RuneCountInString(fcontent)
  660. } else if n.IsContainerBlock() {
  661. markdown, content = treenode.NodeStaticMdContent(n, luteEngine)
  662. fc := treenode.FirstLeafBlock(n)
  663. fcontent = treenode.NodeStaticContent(fc)
  664. parentID = n.Parent.ID
  665. // 将标题块作为父节点
  666. if h := heading(n); nil != h {
  667. parentID = h.ID
  668. }
  669. length = utf8.RuneCountInString(fcontent)
  670. } else {
  671. markdown, content = treenode.NodeStaticMdContent(n, luteEngine)
  672. parentID = n.Parent.ID
  673. // 将标题块作为父节点
  674. if h := heading(n); nil != h {
  675. parentID = h.ID
  676. }
  677. length = utf8.RuneCountInString(content)
  678. }
  679. block = &Block{
  680. ID: n.ID,
  681. ParentID: parentID,
  682. RootID: rootID,
  683. Hash: hash,
  684. Box: boxID,
  685. Path: p,
  686. HPath: tree.HPath,
  687. Name: name,
  688. Alias: alias,
  689. Memo: memo,
  690. Tag: tag,
  691. Content: content,
  692. FContent: fcontent,
  693. Markdown: markdown,
  694. Length: length,
  695. Type: treenode.TypeAbbr(n.Type.String()),
  696. SubType: treenode.SubTypeAbbr(n),
  697. IAL: ialContent,
  698. Sort: nSort(n),
  699. Created: util.TimeFromID(n.ID),
  700. Updated: n.IALAttr("updated"),
  701. }
  702. attrs := parse.IAL2Map(n.KramdownIAL)
  703. for attrName, attrVal := range attrs {
  704. if !isAttr(attrName) {
  705. continue
  706. }
  707. attr := &Attribute{
  708. ID: ast.NewNodeID(),
  709. Name: attrName,
  710. Value: attrVal,
  711. Type: "b",
  712. BlockID: n.ID,
  713. RootID: rootID,
  714. Box: boxID,
  715. Path: p,
  716. }
  717. attributes = append(attributes, attr)
  718. }
  719. return
  720. }
  721. func tagFromNode(node *ast.Node) (ret string) {
  722. tagBuilder := bytes.Buffer{}
  723. if ast.NodeDocument == node.Type {
  724. tagIAL := html.UnescapeString(node.IALAttr("tags"))
  725. tags := strings.Split(tagIAL, ",")
  726. for _, t := range tags {
  727. t = strings.TrimSpace(t)
  728. if "" == t {
  729. continue
  730. }
  731. tagBuilder.WriteString("#")
  732. tagBuilder.WriteString(t)
  733. tagBuilder.WriteString("# ")
  734. }
  735. return strings.TrimSpace(tagBuilder.String())
  736. }
  737. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  738. if !entering {
  739. return ast.WalkContinue
  740. }
  741. if ast.NodeTag == n.Type {
  742. tagBuilder.WriteString("#")
  743. tagBuilder.WriteString(n.Text())
  744. tagBuilder.WriteString("# ")
  745. }
  746. return ast.WalkContinue
  747. })
  748. return strings.TrimSpace(tagBuilder.String())
  749. }
  750. func heading(node *ast.Node) *ast.Node {
  751. currentLevel := 16
  752. if ast.NodeHeading == node.Type {
  753. currentLevel = node.HeadingLevel
  754. } else if ast.NodeSuperBlock == node.Type {
  755. superBlockHeading := treenode.SuperBlockHeading(node)
  756. if nil != superBlockHeading {
  757. node = superBlockHeading
  758. currentLevel = node.HeadingLevel
  759. }
  760. }
  761. for prev := node.Previous; nil != prev; prev = prev.Previous {
  762. if ast.NodeHeading == prev.Type {
  763. if prev.HeadingLevel < currentLevel {
  764. return prev
  765. }
  766. }
  767. }
  768. return nil
  769. }
  770. func DeleteBlockByIDs(tx *sql.Tx, ids []string) (err error) {
  771. return deleteBlocksByIDs(tx, ids)
  772. }
  773. func DeleteByBoxTx(tx *sql.Tx, box string) (err error) {
  774. if err = deleteBlocksByBoxTx(tx, box); nil != err {
  775. return
  776. }
  777. if err = deleteSpansByBoxTx(tx, box); nil != err {
  778. return
  779. }
  780. if err = deleteAssetsByBoxTx(tx, box); nil != err {
  781. return
  782. }
  783. if err = deleteAttributesByBoxTx(tx, box); nil != err {
  784. return
  785. }
  786. if err = deleteRefsByBoxTx(tx, box); nil != err {
  787. return
  788. }
  789. if err = deleteFileAnnotationRefsByBoxTx(tx, box); nil != err {
  790. return
  791. }
  792. return
  793. }
  794. func deleteBlocksByIDs(tx *sql.Tx, ids []string) (err error) {
  795. in := bytes.Buffer{}
  796. in.Grow(4096)
  797. in.WriteString("(")
  798. for i, id := range ids {
  799. in.WriteString("'")
  800. in.WriteString(id)
  801. in.WriteString("'")
  802. if i < len(ids)-1 {
  803. in.WriteString(",")
  804. }
  805. removeBlockCache(id)
  806. }
  807. in.WriteString(")")
  808. stmt := "DELETE FROM blocks WHERE id IN " + in.String()
  809. if err = execStmtTx(tx, stmt); nil != err {
  810. return
  811. }
  812. stmt = "DELETE FROM blocks_fts WHERE id IN " + in.String()
  813. if err = execStmtTx(tx, stmt); nil != err {
  814. return
  815. }
  816. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE id IN " + in.String()
  817. if err = execStmtTx(tx, stmt); nil != err {
  818. return
  819. }
  820. return
  821. }
  822. func deleteBlocksByBoxTx(tx *sql.Tx, box string) (err error) {
  823. stmt := "DELETE FROM blocks WHERE box = ?"
  824. if err = execStmtTx(tx, stmt, box); nil != err {
  825. return
  826. }
  827. stmt = "DELETE FROM blocks_fts WHERE box = ?"
  828. if err = execStmtTx(tx, stmt, box); nil != err {
  829. return
  830. }
  831. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE box = ?"
  832. if err = execStmtTx(tx, stmt, box); nil != err {
  833. return
  834. }
  835. ClearBlockCache()
  836. return
  837. }
  838. func deleteSpansByPathTx(tx *sql.Tx, box, path string) (err error) {
  839. stmt := "DELETE FROM spans WHERE box = ? AND path = ?"
  840. err = execStmtTx(tx, stmt, box, path)
  841. return
  842. }
  843. func deleteSpansByRootID(tx *sql.Tx, rootID string) (err error) {
  844. stmt := "DELETE FROM spans WHERE root_id =?"
  845. err = execStmtTx(tx, stmt, rootID)
  846. return
  847. }
  848. func deleteSpansByBoxTx(tx *sql.Tx, box string) (err error) {
  849. stmt := "DELETE FROM spans WHERE box = ?"
  850. err = execStmtTx(tx, stmt, box)
  851. return
  852. }
  853. func deleteAssetsByPathTx(tx *sql.Tx, box, path string) (err error) {
  854. stmt := "DELETE FROM assets WHERE box = ? AND docpath = ?"
  855. err = execStmtTx(tx, stmt, box, path)
  856. return
  857. }
  858. func deleteAttributeByBlockID(tx *sql.Tx, blockID string) (err error) {
  859. stmt := "DELETE FROM attributes WHERE block_id = ?"
  860. err = execStmtTx(tx, stmt, blockID)
  861. return
  862. }
  863. func deleteAttributesByPathTx(tx *sql.Tx, box, path string) (err error) {
  864. stmt := "DELETE FROM attributes WHERE box = ? AND path = ?"
  865. err = execStmtTx(tx, stmt, box, path)
  866. return
  867. }
  868. func deleteAssetsByBoxTx(tx *sql.Tx, box string) (err error) {
  869. stmt := "DELETE FROM assets WHERE box = ?"
  870. err = execStmtTx(tx, stmt, box)
  871. return
  872. }
  873. func deleteAttributesByBoxTx(tx *sql.Tx, box string) (err error) {
  874. stmt := "DELETE FROM attributes WHERE box = ?"
  875. err = execStmtTx(tx, stmt, box)
  876. return
  877. }
  878. func deleteRefsByPath(tx *sql.Tx, box, path string) (err error) {
  879. stmt := "DELETE FROM refs WHERE box = ? AND path = ?"
  880. err = execStmtTx(tx, stmt, box, path)
  881. return
  882. }
  883. func deleteRefsByPathTx(tx *sql.Tx, box, path string) (err error) {
  884. stmt := "DELETE FROM refs WHERE box = ? AND path = ?"
  885. err = execStmtTx(tx, stmt, box, path)
  886. return
  887. }
  888. func DeleteRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  889. if err = deleteFileAnnotationRefsByBoxTx(tx, box); nil != err {
  890. return
  891. }
  892. return deleteRefsByBoxTx(tx, box)
  893. }
  894. func deleteRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  895. stmt := "DELETE FROM refs WHERE box = ?"
  896. err = execStmtTx(tx, stmt, box)
  897. return
  898. }
  899. func deleteFileAnnotationRefsByPath(tx *sql.Tx, box, path string) (err error) {
  900. stmt := "DELETE FROM file_annotation_refs WHERE box = ? AND path = ?"
  901. err = execStmtTx(tx, stmt, box, path)
  902. return
  903. }
  904. func deleteFileAnnotationRefsByPathTx(tx *sql.Tx, box, path string) (err error) {
  905. stmt := "DELETE FROM file_annotation_refs WHERE box = ? AND path = ?"
  906. err = execStmtTx(tx, stmt, box, path)
  907. return
  908. }
  909. func deleteFileAnnotationRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  910. stmt := "DELETE FROM file_annotation_refs WHERE box = ?"
  911. err = execStmtTx(tx, stmt, box)
  912. return
  913. }
  914. func DeleteByRootID(tx *sql.Tx, rootID string) (err error) {
  915. stmt := "DELETE FROM blocks WHERE root_id = ?"
  916. if err = execStmtTx(tx, stmt, rootID); nil != err {
  917. return
  918. }
  919. stmt = "DELETE FROM spans WHERE root_id = ?"
  920. if err = execStmtTx(tx, stmt, rootID); nil != err {
  921. return
  922. }
  923. stmt = "DELETE FROM assets WHERE root_id = ?"
  924. if err = execStmtTx(tx, stmt, rootID); nil != err {
  925. return
  926. }
  927. stmt = "DELETE FROM refs WHERE root_id = ?"
  928. if err = execStmtTx(tx, stmt, rootID); nil != err {
  929. return
  930. }
  931. stmt = "DELETE FROM file_annotation_refs WHERE root_id = ?"
  932. if err = execStmtTx(tx, stmt, rootID); nil != err {
  933. return
  934. }
  935. ClearBlockCache()
  936. return
  937. }
  938. func batchDeleteByPathPrefix(tx *sql.Tx, boxID, pathPrefix string) (err error) {
  939. stmt := "DELETE FROM blocks WHERE box = ? AND path LIKE ?"
  940. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  941. return
  942. }
  943. stmt = "DELETE FROM blocks_fts WHERE box = ? AND path LIKE ?"
  944. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  945. return
  946. }
  947. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE box = ? AND path LIKE ?"
  948. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  949. return
  950. }
  951. stmt = "DELETE FROM spans WHERE box = ? AND path LIKE ?"
  952. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  953. return
  954. }
  955. stmt = "DELETE FROM assets WHERE box = ? AND docpath LIKE ?"
  956. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  957. return
  958. }
  959. stmt = "DELETE FROM refs WHERE box = ? AND path LIKE ?"
  960. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  961. return
  962. }
  963. stmt = "DELETE FROM file_annotation_refs WHERE box = ? AND path LIKE ?"
  964. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  965. return
  966. }
  967. ClearBlockCache()
  968. return
  969. }
  970. func batchUpdateHPath(tx *sql.Tx, boxID, rootID, oldHPath, newHPath string) (err error) {
  971. stmt := "UPDATE blocks SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  972. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  973. return
  974. }
  975. stmt = "UPDATE blocks_fts SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  976. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  977. return
  978. }
  979. stmt = "UPDATE blocks_fts_case_insensitive SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  980. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  981. return
  982. }
  983. ClearBlockCache()
  984. return
  985. }
  986. func CloseDatabase() {
  987. if err := db.Close(); nil != err {
  988. logging.LogErrorf("close database failed: %s", err)
  989. return
  990. }
  991. if err := historyDB.Close(); nil != err {
  992. logging.LogErrorf("close history database failed: %s", err)
  993. return
  994. }
  995. logging.LogInfof("closed database")
  996. }
  997. func queryRow(query string, args ...interface{}) *sql.Row {
  998. query = strings.TrimSpace(query)
  999. if "" == query {
  1000. logging.LogErrorf("statement is empty")
  1001. return nil
  1002. }
  1003. return db.QueryRow(query, args...)
  1004. }
  1005. func query(query string, args ...interface{}) (*sql.Rows, error) {
  1006. query = strings.TrimSpace(query)
  1007. if "" == query {
  1008. return nil, errors.New("statement is empty")
  1009. }
  1010. return db.Query(query, args...)
  1011. }
  1012. func BeginTx() (tx *sql.Tx, err error) {
  1013. if tx, err = db.Begin(); nil != err {
  1014. logging.LogErrorf("begin tx failed: %s\n %s", err, logging.ShortStack())
  1015. if strings.Contains(err.Error(), "database is locked") {
  1016. os.Exit(util.ExitCodeReadOnlyDatabase)
  1017. }
  1018. }
  1019. return
  1020. }
  1021. func BeginHistoryTx() (tx *sql.Tx, err error) {
  1022. if tx, err = historyDB.Begin(); nil != err {
  1023. logging.LogErrorf("begin history tx failed: %s\n %s", err, logging.ShortStack())
  1024. if strings.Contains(err.Error(), "database is locked") {
  1025. os.Exit(util.ExitCodeReadOnlyDatabase)
  1026. }
  1027. }
  1028. return
  1029. }
  1030. func CommitTx(tx *sql.Tx) (err error) {
  1031. if nil == tx {
  1032. logging.LogErrorf("tx is nil")
  1033. return errors.New("tx is nil")
  1034. }
  1035. if err = tx.Commit(); nil != err {
  1036. logging.LogErrorf("commit tx failed: %s\n %s", err, logging.ShortStack())
  1037. }
  1038. return
  1039. }
  1040. func RollbackTx(tx *sql.Tx) {
  1041. if err := tx.Rollback(); nil != err {
  1042. logging.LogErrorf("rollback tx failed: %s\n %s", err, logging.ShortStack())
  1043. }
  1044. }
  1045. func prepareExecInsertTx(tx *sql.Tx, stmtSQL string, args []interface{}) (err error) {
  1046. stmt, err := tx.Prepare(stmtSQL)
  1047. if nil != err {
  1048. return
  1049. }
  1050. if _, err = stmt.Exec(args...); nil != err {
  1051. logging.LogErrorf("exec database stmt [%s] failed: %s", stmtSQL, err)
  1052. return
  1053. }
  1054. return
  1055. }
  1056. func execStmtTx(tx *sql.Tx, stmt string, args ...interface{}) (err error) {
  1057. if _, err = tx.Exec(stmt, args...); nil != err {
  1058. if strings.Contains(err.Error(), "database disk image is malformed") {
  1059. tx.Rollback()
  1060. db.Close()
  1061. removeDatabaseFile()
  1062. logging.LogFatalf("database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it", util.DBPath)
  1063. }
  1064. logging.LogErrorf("exec database stmt [%s] failed: %s\n %s", stmt, err, logging.ShortStack())
  1065. return
  1066. }
  1067. return
  1068. }
  1069. func nSort(n *ast.Node) int {
  1070. switch n.Type {
  1071. // 以下为块级元素
  1072. case ast.NodeDocument:
  1073. return 0
  1074. case ast.NodeHeading:
  1075. return 5
  1076. case ast.NodeParagraph:
  1077. return 10
  1078. case ast.NodeCodeBlock:
  1079. return 10
  1080. case ast.NodeMathBlock:
  1081. return 10
  1082. case ast.NodeTable:
  1083. return 10
  1084. case ast.NodeHTMLBlock:
  1085. return 10
  1086. case ast.NodeList:
  1087. return 20
  1088. case ast.NodeListItem:
  1089. return 20
  1090. case ast.NodeBlockquote:
  1091. return 20
  1092. case ast.NodeSuperBlock:
  1093. return 30
  1094. // 以下为行级元素
  1095. case ast.NodeText, ast.NodeTextMark:
  1096. return 200
  1097. case ast.NodeTag:
  1098. return 205
  1099. }
  1100. return 100
  1101. }
  1102. func ialAttr(ial, name string) (ret string) {
  1103. idx := strings.Index(ial, name)
  1104. if 0 > idx {
  1105. return ""
  1106. }
  1107. ret = ial[idx+len(name)+2:]
  1108. ret = ret[:strings.Index(ret, "\"")]
  1109. return
  1110. }
  1111. func IsAssetLinkDest(dest []byte) bool {
  1112. return bytes.HasPrefix(dest, []byte("assets/"))
  1113. }
  1114. func removeDatabaseFile() (err error) {
  1115. err = os.RemoveAll(util.DBPath)
  1116. if nil != err {
  1117. return
  1118. }
  1119. err = os.RemoveAll(util.DBPath + "-shm")
  1120. if nil != err {
  1121. return
  1122. }
  1123. err = os.RemoveAll(util.DBPath + "-wal")
  1124. if nil != err {
  1125. return
  1126. }
  1127. return
  1128. }