{"record":{"id":"ea01670c060218fe","repo":"siyuan-note/siyuan","slug":"invalid-template-document-attributes","errorCode":null,"errorMessage":"invalid template document attributes","messagePattern":"invalid template document attributes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_export_attrs.go","lineNumber":51,"sourceCode":"\tif tree == nil {\n\t\treturn nil, errors.New(\"parse template tree failed\")\n\t}\n\tnormalizeTree(tree)\n\tif err := applyTemplateDocumentAttributes(tree); err != nil {\n\t\treturn nil, err\n\t}\n\treturn tree, nil\n}\n\n// 独立文档属性保留在原位置求值，渲染完成后再合并到根节点。\nfunc applyTemplateDocumentAttributes(tree *parse.Tree) error {\n\tfor node := tree.Root.FirstChild; node != nil; {\n\t\tnext := node.Next\n\t\tinfo := node.ChildByType(ast.NodeCodeBlockFenceInfoMarker)\n\t\tif node.Type == ast.NodeCodeBlock && info != nil && string(info.CodeBlockInfo) == templateDocumentAttributeMarker {\n\t\t\tcode := node.ChildByType(ast.NodeCodeBlockCode)\n\t\t\tif code == nil {\n\t\t\t\treturn errors.New(\"invalid template document attributes\")\n\t\t\t}\n\t\t\tsource := strings.TrimSpace(string(code.Tokens))\n\t\t\tattrs := parseTemplateDocumentAttributes(source)\n\t\t\tif len(attrs) == 0 {\n\t\t\t\treturn errors.New(\"invalid template document attributes\")\n\t\t\t}\n\t\t\tfor _, attr := range attrs {\n\t\t\t\tif attr[0] == \"id\" || attr[0] == \"updated\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttree.Root.RemoveIALAttr(attr[0])\n\t\t\t\ttree.Root.KramdownIAL = append(tree.Root.KramdownIAL, attr)\n\t\t\t}\n\t\t\tif next != nil && next.Type == ast.NodeKramdownBlockIAL && parse.IAL2Map(parse.Tokens2IAL(next.Tokens))[\"type\"] != \"doc\" {\n\t\t\t\tial := next\n\t\t\t\tnext = next.Next\n\t\t\t\tial.Unlink()\n\t\t\t}","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_export_attrs.go#L33-L69","documentation":"A fenced code block marked with the siyuan-template-doc-attrs-v1 marker was found but has no code body node. The document-attributes block is malformed: the fence info marker exists but the NodeCodeBlockCode child is missing, so the attribute payload cannot be extracted.","triggerScenarios":"applyTemplateDocumentAttributes encounters a code block whose info marker equals templateDocumentAttributeMarker but ChildByType(ast.NodeCodeBlockCode) returns nil — e.g. an empty or hand-edited fence block. Called via parseTemplateKTree from renderTemplateSource and renderTemplateDocTreeMarkdown.","commonSituations":"Hand-editing a template and leaving the marker fence without a body; a tool stripped the code content but kept the info marker; copy/paste lost the fenced body.","solutions":["Add the attribute payload body inside the marker code block, or remove the marker code block entirely","Use the app's save-as-template flow to regenerate a well-formed attributes block","Do not hand-edit the siyuan-template-doc-attrs-v1 fence"],"exampleFix":"// before\n```siyuan-template-doc-attrs-v1\n```\n// after (either fill the body or remove the block)\n```siyuan-template-doc-attrs-v1\nname: my-template\n```\n","handlingStrategy":"validation","validationCode":"// ensure the marker fence has a non-empty body before parsing\nif strings.Contains(string(md), \"siyuan-template-doc-attrs-v1\") {\n    var inBlock, hasBody bool\n    for _, line := range strings.Split(string(md), \"\\n\") {\n        if strings.HasPrefix(strings.TrimSpace(line), \"```siyuan-template-doc-attrs-v1\") { inBlock = true; continue }\n        if inBlock && strings.TrimSpace(line) == \"```\" { break }\n        if inBlock && strings.TrimSpace(line) != \"\" { hasBody = true }\n    }\n    if !hasBody { return errors.New(\"doc-attrs fence has no body\") }\n}","typeGuard":null,"tryCatchPattern":"if err := applyTemplateDocumentAttributes(tree); err != nil {\n    return fmt.Errorf(\"fix or remove the siyuan-template-doc-attrs-v1 block: %w\", err)\n}","preventionTips":["Never hand-edit the siyuan-template-doc-attrs-v1 fence","Regenerate templates via the app's save-as-template flow","Keep the fence body non-empty whenever the marker is present"],"tags":["template","validation","fenced-code-block"],"backgroundTag":"schema-validation-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}