This commit is contained in:
Liang Ding 2023-01-27 21:14:09 +08:00
parent 9589dbbc7b
commit 04f47749f7
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
19 changed files with 295 additions and 34 deletions

File diff suppressed because one or more lines are too long

View file

@ -17,12 +17,12 @@
package api
import (
"github.com/siyuan-note/siyuan/kernel/treenode"
"net/http"
"github.com/88250/gulu"
"github.com/88250/lute"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
@ -299,7 +299,7 @@ func dataBlockDOM(data string, luteEngine *lute.Lute) (ret string) {
ret = luteEngine.Md2BlockDOM(data, true)
if "" == ret {
// 使用 API 插入空字符串出现错误 https://github.com/siyuan-note/siyuan/issues/3931
blankParagraph := parse.NewParagraph()
blankParagraph := treenode.NewParagraph()
ret = lute.RenderNodeBlockDOM(blankParagraph, luteEngine.ParseOptions, luteEngine.RenderOptions)
}
return

View file

@ -17,6 +17,7 @@
package api
import (
"github.com/siyuan-note/siyuan/kernel/treenode"
"net/http"
"path/filepath"
"strings"
@ -70,7 +71,7 @@ func html2BlockDOM(c *gin.Context) {
}
if ast.NodeListItem == n.Type && nil == n.FirstChild {
newNode := parse.NewParagraph()
newNode := treenode.NewParagraph()
n.AppendChild(newNode)
n.SetIALAttr("updated", util.TimeFromID(newNode.ID))
return ast.WalkSkipChildren

View file

@ -0,0 +1,245 @@
// Lute - 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript
// Copyright (c) 2019-present, b3log.org
//
// Lute is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
package filesys
import (
"bytes"
"strings"
"github.com/88250/lute/ast"
"github.com/88250/lute/editor"
"github.com/88250/lute/parse"
"github.com/88250/lute/util"
"github.com/goccy/go-json"
"github.com/siyuan-note/siyuan/kernel/treenode"
)
func ParseJSONWithoutFix(jsonData []byte, options *parse.Options) (ret *parse.Tree, err error) {
root := &ast.Node{}
err = json.Unmarshal(jsonData, root)
if nil != err {
return
}
ret = &parse.Tree{Name: "", ID: root.ID, Root: &ast.Node{Type: ast.NodeDocument, ID: root.ID, Spec: root.Spec}, Context: &parse.Context{ParseOption: options}}
ret.Root.KramdownIAL = parse.Map2IAL(root.Properties)
ret.Context.Tip = ret.Root
if nil == root.Children {
return
}
idMap := map[string]bool{}
for _, child := range root.Children {
genTreeByJSON(child, ret, &idMap, nil, nil, true)
}
return
}
func ParseJSON(jsonData []byte, options *parse.Options) (ret *parse.Tree, needFix bool, err error) {
root := &ast.Node{}
err = json.Unmarshal(jsonData, root)
if nil != err {
return
}
ret = &parse.Tree{Name: "", ID: root.ID, Root: &ast.Node{Type: ast.NodeDocument, ID: root.ID, Spec: root.Spec}, Context: &parse.Context{ParseOption: options}}
ret.Root.KramdownIAL = parse.Map2IAL(root.Properties)
for _, kv := range ret.Root.KramdownIAL {
if strings.Contains(kv[1], "\n") {
val := kv[1]
val = strings.ReplaceAll(val, "\n", editor.IALValEscNewLine)
ret.Root.SetIALAttr(kv[0], val)
needFix = true
}
}
ret.Context.Tip = ret.Root
if nil == root.Children {
newPara := &ast.Node{Type: ast.NodeParagraph, ID: ast.NewNodeID()}
newPara.SetIALAttr("id", newPara.ID)
ret.Root.AppendChild(newPara)
needFix = true
return
}
needMigrate2Spec1 := false
idMap := map[string]bool{}
for _, child := range root.Children {
genTreeByJSON(child, ret, &idMap, &needFix, &needMigrate2Spec1, false)
}
if nil == ret.Root.FirstChild {
// 如果是空文档的话挂一个空段落上去
newP := treenode.NewParagraph()
ret.Root.AppendChild(newP)
ret.Root.SetIALAttr("updated", newP.ID[:14])
}
if needMigrate2Spec1 {
parse.NestedInlines2FlattedSpans(ret)
needFix = true
}
return
}
func genTreeByJSON(node *ast.Node, tree *parse.Tree, idMap *map[string]bool, needFix, needMigrate2Spec1 *bool, ignoreFix bool) {
node.Tokens, node.Type = util.StrToBytes(node.Data), ast.Str2NodeType(node.TypeStr)
node.Data, node.TypeStr = "", ""
node.KramdownIAL = parse.Map2IAL(node.Properties)
node.Properties = nil
if !ignoreFix {
// 历史数据订正
if -1 == node.Type {
*needFix = true
node.Type = ast.NodeParagraph
node.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: node.Tokens})
node.Children = nil
}
switch node.Type {
case ast.NodeList:
if 1 > len(node.Children) {
*needFix = true
return // 忽略空列表
}
case ast.NodeListItem:
if 1 > len(node.Children) {
*needFix = true
return // 忽略空列表项
}
case ast.NodeBlockquote:
if 2 > len(node.Children) {
*needFix = true
return // 忽略空引述
}
case ast.NodeSuperBlock:
if 4 > len(node.Children) {
*needFix = true
return // 忽略空超级块
}
case ast.NodeMathBlock:
if 1 > len(node.Children) {
*needFix = true
return // 忽略空公式
}
case ast.NodeBlockQueryEmbed:
if 1 > len(node.Children) {
*needFix = true
return // 忽略空查询嵌入块
}
}
fixLegacyData(tree.Context.Tip, node, idMap, needFix, needMigrate2Spec1)
}
tree.Context.Tip.AppendChild(node)
tree.Context.Tip = node
defer tree.Context.ParentTip()
if nil == node.Children {
return
}
for _, child := range node.Children {
genTreeByJSON(child, tree, idMap, needFix, needMigrate2Spec1, ignoreFix)
}
node.Children = nil
}
func fixLegacyData(tip, node *ast.Node, idMap *map[string]bool, needFix, needMigrate2Spec1 *bool) {
if node.IsBlock() {
if "" == node.ID {
node.ID = ast.NewNodeID()
node.SetIALAttr("id", node.ID)
*needFix = true
}
if 0 < len(node.Children) && ast.NodeBr.String() == node.Children[len(node.Children)-1].TypeStr {
// 剔除块尾多余的软换行 https://github.com/siyuan-note/siyuan/issues/6191
node.Children = node.Children[:len(node.Children)-1]
*needFix = true
}
}
if "" != node.ID {
if _, ok := (*idMap)[node.ID]; ok {
node.ID = ast.NewNodeID()
node.SetIALAttr("id", node.ID)
*needFix = true
}
(*idMap)[node.ID] = true
}
switch node.Type {
case ast.NodeIFrame:
if bytes.Contains(node.Tokens, util.StrToBytes("iframe-content")) {
start := bytes.Index(node.Tokens, util.StrToBytes("<iframe"))
end := bytes.Index(node.Tokens, util.StrToBytes("</iframe>"))
node.Tokens = node.Tokens[start : end+9]
*needFix = true
}
case ast.NodeWidget:
if bytes.Contains(node.Tokens, util.StrToBytes("http://127.0.0.1:6806")) {
node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("http://127.0.0.1:6806"), nil)
*needFix = true
}
case ast.NodeList:
if nil != node.ListData && 3 != node.ListData.Typ && 0 < len(node.Children) &&
nil != node.Children[0].ListData && 3 == node.Children[0].ListData.Typ {
node.ListData.Typ = 3
*needFix = true
}
case ast.NodeMark:
if 3 == len(node.Children) && "NodeText" == node.Children[1].TypeStr {
if strings.HasPrefix(node.Children[1].Data, " ") || strings.HasSuffix(node.Children[1].Data, " ") {
node.Children[1].Data = strings.TrimSpace(node.Children[1].Data)
*needFix = true
}
}
case ast.NodeHeading:
if 6 < node.HeadingLevel {
node.HeadingLevel = 6
*needFix = true
}
case ast.NodeLinkDest:
if bytes.HasPrefix(node.Tokens, []byte("assets/")) && bytes.HasSuffix(node.Tokens, []byte(" ")) {
node.Tokens = bytes.TrimSpace(node.Tokens)
*needFix = true
}
case ast.NodeText:
if nil != tip.LastChild && ast.NodeTagOpenMarker == tip.LastChild.Type && 1 > len(node.Tokens) {
node.Tokens = []byte("Untitled")
*needFix = true
}
case ast.NodeTagCloseMarker:
if nil != tip.LastChild {
if ast.NodeTagOpenMarker == tip.LastChild.Type {
tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: []byte("Untitled")})
*needFix = true
} else if "" == tip.LastChild.Text() {
tip.LastChild.Type = ast.NodeText
tip.LastChild.Tokens = []byte("Untitled")
*needFix = true
}
}
case ast.NodeBlockRef:
// 建立索引时无法解析 `v2.2.0-` 版本的块引用 https://github.com/siyuan-note/siyuan/issues/6889
// 早先的迁移程序有缺陷,漏迁移了块引用节点,这里检测到块引用节点后标识需要迁移
*needMigrate2Spec1 = true
}
for _, kv := range node.KramdownIAL {
if strings.Contains(kv[1], "\n") {
val := kv[1]
val = strings.ReplaceAll(val, "\n", editor.IALValEscNewLine)
node.SetIALAttr(kv[0], val)
*needFix = true
}
}
}

View file

@ -151,7 +151,7 @@ func prepareWriteTree(tree *parse.Tree) (data []byte, filePath string, err error
luteEngine := util.NewLute() // 不关注用户的自定义解析渲染选项
if nil == tree.Root.FirstChild {
newP := parse.NewParagraph()
newP := treenode.NewParagraph()
tree.Root.AppendChild(newP)
tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID))
treenode.IndexBlockTree(tree)
@ -229,7 +229,7 @@ func recoverParseJSON2Tree(boxID, p, filePath string, luteEngine *lute.Lute) (re
func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (ret *parse.Tree) {
var err error
var needFix bool
ret, needFix, err = parse.ParseJSON(jsonData, luteEngine.ParseOptions)
ret, needFix, err = ParseJSON(jsonData, luteEngine.ParseOptions)
if nil != err {
logging.LogErrorf("parse json [%s] to tree failed: %s", boxID+p, err)
return

View file

@ -6,7 +6,7 @@ require (
github.com/88250/clipboard v0.1.5
github.com/88250/css v0.1.2
github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798
github.com/88250/lute v1.7.5
github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a
github.com/88250/pdfcpu v0.3.13
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732
@ -27,6 +27,7 @@ require (
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.8.2
github.com/goccy/go-json v0.10.0
github.com/gofrs/flock v0.8.1
github.com/imroc/req/v3 v3.30.0
github.com/jinzhu/copier v0.3.5
@ -73,7 +74,6 @@ require (
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/google/uuid v1.3.0 // indirect

View file

@ -17,8 +17,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5 h1:8HdZozCsXS
github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798 h1:sR/s/Y9wyl79ZRCUERwLPo9zqaB3KhNRodCMTJ4ozEU=
github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
github.com/88250/lute v1.7.5 h1:mcPFURh5sK1WH1kFRjqK5DkMWOfVN2BhyrXitN8GmpQ=
github.com/88250/lute v1.7.5/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a h1:zqWeg1AOmN2X9tD0AD0V5BE40eQOOxuFqB0LSGiw6tQ=
github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
github.com/88250/pdfcpu v0.3.13/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=

View file

@ -138,7 +138,7 @@ func SwapBlockRef(refID, defID string, includeChildren bool) (err error) {
}
}
refPivot := parse.NewParagraph()
refPivot := treenode.NewParagraph()
refNode.InsertBefore(refPivot)
if ast.NodeListItem == defNode.Type {

View file

@ -425,7 +425,7 @@ func parseKTree(kramdown []byte) (ret *parse.Tree) {
func genTreeID(tree *parse.Tree) {
if nil == tree.Root.FirstChild {
tree.Root.AppendChild(parse.NewParagraph())
tree.Root.AppendChild(treenode.NewParagraph())
}
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {

View file

@ -447,6 +447,11 @@ func getMarkSpanEnd() string {
}
func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int, isBacklink bool) (blockCount, childBlockCount int, dom, parentID, parent2ID, rootID, typ string, eof bool, boxID, docPath string, isBacklinkExpand bool, err error) {
//os.MkdirAll("pprof", 0755)
//cpuProfile, _ := os.Create("pprof/GetDoc")
//pprof.StartCPUProfile(cpuProfile)
//defer pprof.StopCPUProfile()
WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
inputIndex := index
@ -1451,7 +1456,7 @@ func createDoc(boxID, p, title, dom string) (tree *parse.Tree, err error) {
updated := util.TimeFromID(id)
tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
if nil == tree.Root.FirstChild {
tree.Root.AppendChild(parse.NewParagraph())
tree.Root.AppendChild(treenode.NewParagraph())
}
transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}

View file

@ -166,7 +166,7 @@ func Doc2Heading(srcID, targetID string, after bool) (srcTreeBox, srcTreePath st
if "" != tagIAL && 0 < len(tags) {
// 带标签的文档块转换为标题块时将标签移动到标题块下方 https://github.com/siyuan-note/siyuan/issues/6550
tagPara := parse.NewParagraph()
tagPara := treenode.NewParagraph()
for i, tag := range tags {
if "" == tag {
continue
@ -319,7 +319,7 @@ func Heading2Doc(srcHeadingID, targetBoxID, targetPath string) (srcRootBlockID,
headingNode.Unlink()
srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
if nil == srcTree.Root.FirstChild {
srcTree.Root.AppendChild(parse.NewParagraph())
srcTree.Root.AppendChild(treenode.NewParagraph())
}
if err = indexWriteJSONQueue(srcTree); nil != err {
return "", "", err

View file

@ -19,7 +19,6 @@ package model
import (
"encoding/json"
"fmt"
"github.com/siyuan-note/siyuan/kernel/task"
"io/fs"
"math"
"os"
@ -37,8 +36,10 @@ import (
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/search"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
)
@ -154,7 +155,7 @@ func GetDocHistoryContent(historyPath, keyword string) (id, rootID, content stri
isLargeDoc = 1024*1024*1 <= len(data)
luteEngine := NewLute()
historyTree, err := parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
historyTree, err := filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if nil != err {
logging.LogErrorf("parse tree from file [%s] failed, remove it", historyPath)
os.RemoveAll(historyPath)

View file

@ -45,6 +45,7 @@ import (
"github.com/88250/lute/render"
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
@ -125,7 +126,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
err = readErr
return
}
tree, _, parseErr := parse.ParseJSON(data, luteEngine.ParseOptions)
tree, _, parseErr := filesys.ParseJSON(data, luteEngine.ParseOptions)
if nil != parseErr {
logging.LogErrorf("parse .sy [%s] failed: %s", syPath, parseErr)
err = parseErr

View file

@ -72,7 +72,7 @@ func ListItem2Doc(srcListItemID, targetBoxID, targetPath string) (srcRootBlockID
children = append(children, c)
}
if 1 > len(children) {
newNode := parse.NewParagraph()
newNode := treenode.NewParagraph()
children = append(children, newNode)
}
@ -96,7 +96,7 @@ func ListItem2Doc(srcListItemID, targetBoxID, targetPath string) (srcRootBlockID
}
srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
if nil == srcTree.Root.FirstChild {
srcTree.Root.AppendChild(parse.NewParagraph())
srcTree.Root.AppendChild(treenode.NewParagraph())
}
if err = indexWriteJSONQueue(srcTree); nil != err {
return "", "", err

View file

@ -23,7 +23,6 @@ import (
"encoding/base64"
"errors"
"fmt"
"github.com/siyuan-note/siyuan/kernel/task"
"math"
"net/http"
"os"
@ -49,7 +48,9 @@ import (
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/cache"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
"github.com/studio-b12/gowebdav"
@ -255,7 +256,7 @@ func parseTitleInSnapshot(fileID string, repo *dejavu.Repo, luteEngine *lute.Lut
}
var tree *parse.Tree
tree, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
tree, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if nil != err {
logging.LogErrorf("parse file [%s] failed: %s", fileID, err)
return
@ -277,7 +278,7 @@ func parseTreeInSnapshot(fileID string, repo *dejavu.Repo, luteEngine *lute.Lute
}
isLargeDoc = 1024*1024*1 <= len(data)
tree, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
tree, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if nil != err {
return
}

View file

@ -20,13 +20,6 @@ import (
"bytes"
"errors"
"fmt"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/88250/lute/render"
"github.com/araddon/dateparse"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
"io/fs"
"os"
"path/filepath"
@ -36,9 +29,16 @@ import (
"time"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/88250/lute/render"
sprig "github.com/Masterminds/sprig/v3"
"github.com/araddon/dateparse"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/search"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
)
func RenderGoTemplate(templateContent string) (ret string, err error) {
@ -258,7 +258,7 @@ func renderTemplate(p, id string) (string, error) {
return ast.WalkContinue
})
for _, n := range nodesNeedAppendChild {
n.AppendChild(parse.NewParagraph())
n.AppendChild(treenode.NewParagraph())
}
for _, n := range unlinks {
n.Unlink()

View file

@ -131,7 +131,7 @@ func loadTree(localPath string, luteEngine *lute.Lute) (ret *parse.Tree, err err
return
}
ret, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
ret, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if nil != err {
logging.LogErrorf("parse json to tree [%s] failed: %s", localPath, err)
return

View file

@ -22,9 +22,8 @@ import (
"strings"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/util"
"github.com/siyuan-note/siyuan/kernel/search"
"github.com/siyuan-note/siyuan/kernel/util"
)
func SearchWidget(keyword string) (ret []*Block) {

View file

@ -114,3 +114,11 @@ func RootChildIDs(rootID string) (ret []string) {
})
return
}
func NewParagraph() (ret *ast.Node) {
newID := ast.NewNodeID()
ret = &ast.Node{ID: newID, Type: ast.NodeParagraph}
ret.SetIALAttr("id", newID)
ret.SetIALAttr("updated", newID[:14])
return
}