database.go 33 KB

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