[{"data":1,"prerenderedAt":871},["ShallowReactive",2],{"post-\u002Fposts\u002F57060fdd":3,"post-nav-\u002Fposts\u002F57060fdd":833,"post-related-\u002Fposts\u002F57060fdd":852},{"id":4,"title":5,"abbrlink":6,"aisummary":7,"body":8,"category":818,"cover":789,"date":819,"description":820,"draft":821,"extension":822,"featured":821,"meta":823,"navigation":389,"path":824,"pinned":389,"seo":825,"stem":826,"tags":827,"updated":831,"__hash__":832},"posts\u002Fposts\u002Fxml-escaping-stress-test.md","特殊字符与代码块压测：& \u003C > \" ' 全都要","57060fdd","文章通过包含特殊字符和代码块来测试 RSS feed 的生成逻辑是否稳定。重点在于 XML 中的特殊字符如 &、\u003C、>、\"、' 必须正确转义，否则会导致解析错误。同时，文章讨论了链接和图片在 feed 中的正确使用方式，以及中文排版中行高和段距的设置建议，以确保阅读体验良好。最后指出，若 feed 在阅读器中显示正常，则说明转义和格式处理正确。",{"type":9,"value":10,"toc":809},"minimark",[11,15,18,22,25,54,65,68,316,329,332,635,638,645,648,661,664,757,760,777,780,783,790,793,796,799,802,805],[12,13,14],"p",{},"这篇文章没有别的目的，就是用来把 feed 生成逻辑撑到极限。",[12,16,17],{},"如果你的 RSS 阅读器能正常显示这一篇，说明转义是对的。",[19,20,21],"h2",{"id":21},"裸字符",[12,23,24],{},"下面这些字符在 XML 里都是危险的，必须转义：",[26,27,28,32,39,45,48],"ul",{},[29,30,31],"li",{},"与号：AT&T、Q&A、R&D",[29,33,34,35],{},"小于号：",[36,37,38],"code",{},"a \u003C b",[29,40,41,42],{},"大于号：",[36,43,44],{},"a > b",[29,46,47],{},"引号：他说 \"这样\" 和 '那样'",[29,49,50,51],{},"CDATA 结束标记本身：",[36,52,53],{},"]]>",[12,55,56,57,60,61,64],{},"如果不转义，",[36,58,59],{},"AT&T"," 会把整个 feed 撑坏——因为解析器会以为 ",[36,62,63],{},"&A"," 是一个实体引用的开始。",[19,66,67],{"id":67},"代码块里的尖括号",[69,70,75],"pre",{"className":71,"code":72,"language":73,"meta":74,"style":74},"language-html shiki shiki-themes github-light github-dark","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh-CN\">\n  \u003Chead>\n    \u003Cmeta charset=\"utf-8\" \u002F>\n    \u003Ctitle>喵洛阁 &amp; 朋友\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cp class=\"note\">如果 a \u003C b 并且 b > c，那么 a \u003C c\u003C\u002Fp>\n    \u003Cscript>\n      const html = '\u003Cdiv class=\"x\">' + text + '\u003C\u002Fdiv>'\n      if (a && b) console.log('&lt;ok&gt;')\n    \u003C\u002Fscript>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html","",[36,76,77,97,117,128,148,170,180,190,223,233,260,287,297,306],{"__ignoreMap":74},[78,79,82,86,90,94],"span",{"class":80,"line":81},"line",1,[78,83,85],{"class":84},"sVt8B","\u003C!",[78,87,89],{"class":88},"s9eBZ","DOCTYPE",[78,91,93],{"class":92},"sScJk"," html",[78,95,96],{"class":84},">\n",[78,98,100,103,105,108,111,115],{"class":80,"line":99},2,[78,101,102],{"class":84},"\u003C",[78,104,73],{"class":88},[78,106,107],{"class":92}," lang",[78,109,110],{"class":84},"=",[78,112,114],{"class":113},"sZZnC","\"zh-CN\"",[78,116,96],{"class":84},[78,118,120,123,126],{"class":80,"line":119},3,[78,121,122],{"class":84},"  \u003C",[78,124,125],{"class":88},"head",[78,127,96],{"class":84},[78,129,131,134,137,140,142,145],{"class":80,"line":130},4,[78,132,133],{"class":84},"    \u003C",[78,135,136],{"class":88},"meta",[78,138,139],{"class":92}," charset",[78,141,110],{"class":84},[78,143,144],{"class":113},"\"utf-8\"",[78,146,147],{"class":84}," \u002F>\n",[78,149,151,153,156,159,163,166,168],{"class":80,"line":150},5,[78,152,133],{"class":84},[78,154,155],{"class":88},"title",[78,157,158],{"class":84},">喵洛阁 ",[78,160,162],{"class":161},"sj4cs","&amp;",[78,164,165],{"class":84}," 朋友\u003C\u002F",[78,167,155],{"class":88},[78,169,96],{"class":84},[78,171,173,176,178],{"class":80,"line":172},6,[78,174,175],{"class":84},"  \u003C\u002F",[78,177,125],{"class":88},[78,179,96],{"class":84},[78,181,183,185,188],{"class":80,"line":182},7,[78,184,122],{"class":84},[78,186,187],{"class":88},"body",[78,189,96],{"class":84},[78,191,193,195,197,200,202,205,208,211,214,216,219,221],{"class":80,"line":192},8,[78,194,133],{"class":84},[78,196,12],{"class":88},[78,198,199],{"class":92}," class",[78,201,110],{"class":84},[78,203,204],{"class":113},"\"note\"",[78,206,207],{"class":84},">如果 a ",[78,209,102],{"class":210},"s7hpK",[78,212,213],{"class":84}," b 并且 b > c，那么 a ",[78,215,102],{"class":210},[78,217,218],{"class":84}," c\u003C\u002F",[78,220,12],{"class":88},[78,222,96],{"class":84},[78,224,226,228,231],{"class":80,"line":225},9,[78,227,133],{"class":84},[78,229,230],{"class":88},"script",[78,232,96],{"class":84},[78,234,236,240,242,245,248,251,254,257],{"class":80,"line":235},10,[78,237,239],{"class":238},"szBVR","      const",[78,241,93],{"class":161},[78,243,244],{"class":238}," =",[78,246,247],{"class":113}," '\u003Cdiv class=\"x\">'",[78,249,250],{"class":238}," +",[78,252,253],{"class":84}," text ",[78,255,256],{"class":238},"+",[78,258,259],{"class":113}," '\u003C\u002Fdiv>'\n",[78,261,263,266,269,272,275,278,281,284],{"class":80,"line":262},11,[78,264,265],{"class":238},"      if",[78,267,268],{"class":84}," (a ",[78,270,271],{"class":238},"&&",[78,273,274],{"class":84}," b) console.",[78,276,277],{"class":92},"log",[78,279,280],{"class":84},"(",[78,282,283],{"class":113},"'&lt;ok&gt;'",[78,285,286],{"class":84},")\n",[78,288,290,293,295],{"class":80,"line":289},12,[78,291,292],{"class":84},"    \u003C\u002F",[78,294,230],{"class":88},[78,296,96],{"class":84},[78,298,300,302,304],{"class":80,"line":299},13,[78,301,175],{"class":84},[78,303,187],{"class":88},[78,305,96],{"class":84},[78,307,309,312,314],{"class":80,"line":308},14,[78,310,311],{"class":84},"\u003C\u002F",[78,313,73],{"class":88},[78,315,96],{"class":84},[12,317,318,319,321,322,321,325,328],{},"上面这块 HTML 里 ",[36,320,102],{},"、",[36,323,324],{},">",[36,326,327],{},"&","、引号全都有。它在页面里应该原样显示，在 feed 里应该被正确转义。",[12,330,331],{},"再来一段 TypeScript：",[69,333,337],{"className":334,"code":335,"language":336,"meta":74,"style":74},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F 这里的箭头函数、泛型尖括号、比较符号都不应该破坏 XML\ntype Predicate\u003CT> = (value: T) => boolean\n\nfunction filterAnd\u003CT>(items: T[], pred: Predicate\u003CT>): T[] {\n  const out: T[] = []\n  for (const item of items) {\n    if (item && pred(item) && (out as unknown[]).length \u003C 100) {\n      out.push(item)\n    }\n  }\n  return out\n}\n\nconst flags = filterAnd([1, 2, 3], (n) => n > 1 && n \u003C 3)\nconsole.log('结果：', flags, '& 完成')\n","ts",[36,338,339,345,385,391,437,457,476,518,529,534,539,547,552,556,614],{"__ignoreMap":74},[78,340,341],{"class":80,"line":81},[78,342,344],{"class":343},"sJ8bj","\u002F\u002F 这里的箭头函数、泛型尖括号、比较符号都不应该破坏 XML\n",[78,346,347,350,353,355,358,361,363,366,370,373,376,379,382],{"class":80,"line":99},[78,348,349],{"class":238},"type",[78,351,352],{"class":92}," Predicate",[78,354,102],{"class":84},[78,356,357],{"class":92},"T",[78,359,360],{"class":84},"> ",[78,362,110],{"class":238},[78,364,365],{"class":84}," (",[78,367,369],{"class":368},"s4XuR","value",[78,371,372],{"class":238},":",[78,374,375],{"class":92}," T",[78,377,378],{"class":84},") ",[78,380,381],{"class":238},"=>",[78,383,384],{"class":161}," boolean\n",[78,386,387],{"class":80,"line":119},[78,388,390],{"emptyLinePlaceholder":389},true,"\n",[78,392,393,396,399,401,403,406,409,411,413,416,419,421,423,425,427,430,432,434],{"class":80,"line":130},[78,394,395],{"class":238},"function",[78,397,398],{"class":92}," filterAnd",[78,400,102],{"class":84},[78,402,357],{"class":92},[78,404,405],{"class":84},">(",[78,407,408],{"class":368},"items",[78,410,372],{"class":238},[78,412,375],{"class":92},[78,414,415],{"class":84},"[], ",[78,417,418],{"class":368},"pred",[78,420,372],{"class":238},[78,422,352],{"class":92},[78,424,102],{"class":84},[78,426,357],{"class":92},[78,428,429],{"class":84},">)",[78,431,372],{"class":238},[78,433,375],{"class":92},[78,435,436],{"class":84},"[] {\n",[78,438,439,442,445,447,449,452,454],{"class":80,"line":150},[78,440,441],{"class":238},"  const",[78,443,444],{"class":161}," out",[78,446,372],{"class":238},[78,448,375],{"class":92},[78,450,451],{"class":84},"[] ",[78,453,110],{"class":238},[78,455,456],{"class":84}," []\n",[78,458,459,462,464,467,470,473],{"class":80,"line":172},[78,460,461],{"class":238},"  for",[78,463,365],{"class":84},[78,465,466],{"class":238},"const",[78,468,469],{"class":161}," item",[78,471,472],{"class":238}," of",[78,474,475],{"class":84}," items) {\n",[78,477,478,481,484,486,489,492,494,497,500,503,506,509,512,515],{"class":80,"line":182},[78,479,480],{"class":238},"    if",[78,482,483],{"class":84}," (item ",[78,485,271],{"class":238},[78,487,488],{"class":92}," pred",[78,490,491],{"class":84},"(item) ",[78,493,271],{"class":238},[78,495,496],{"class":84}," (out ",[78,498,499],{"class":238},"as",[78,501,502],{"class":161}," unknown",[78,504,505],{"class":84},"[]).",[78,507,508],{"class":161},"length",[78,510,511],{"class":238}," \u003C",[78,513,514],{"class":161}," 100",[78,516,517],{"class":84},") {\n",[78,519,520,523,526],{"class":80,"line":192},[78,521,522],{"class":84},"      out.",[78,524,525],{"class":92},"push",[78,527,528],{"class":84},"(item)\n",[78,530,531],{"class":80,"line":225},[78,532,533],{"class":84},"    }\n",[78,535,536],{"class":80,"line":235},[78,537,538],{"class":84},"  }\n",[78,540,541,544],{"class":80,"line":262},[78,542,543],{"class":238},"  return",[78,545,546],{"class":84}," out\n",[78,548,549],{"class":80,"line":289},[78,550,551],{"class":84},"}\n",[78,553,554],{"class":80,"line":299},[78,555,390],{"emptyLinePlaceholder":389},[78,557,558,560,563,565,567,570,573,576,579,581,584,587,590,592,594,597,599,602,605,607,609,612],{"class":80,"line":308},[78,559,466],{"class":238},[78,561,562],{"class":161}," flags",[78,564,244],{"class":238},[78,566,398],{"class":92},[78,568,569],{"class":84},"([",[78,571,572],{"class":161},"1",[78,574,575],{"class":84},", ",[78,577,578],{"class":161},"2",[78,580,575],{"class":84},[78,582,583],{"class":161},"3",[78,585,586],{"class":84},"], (",[78,588,589],{"class":368},"n",[78,591,378],{"class":84},[78,593,381],{"class":238},[78,595,596],{"class":84}," n ",[78,598,324],{"class":238},[78,600,601],{"class":161}," 1",[78,603,604],{"class":238}," &&",[78,606,596],{"class":84},[78,608,102],{"class":238},[78,610,611],{"class":161}," 3",[78,613,286],{"class":84},[78,615,617,620,622,624,627,630,633],{"class":80,"line":616},15,[78,618,619],{"class":84},"console.",[78,621,277],{"class":92},[78,623,280],{"class":84},[78,625,626],{"class":113},"'结果：'",[78,628,629],{"class":84},", flags, ",[78,631,632],{"class":113},"'& 完成'",[78,634,286],{"class":84},[19,636,637],{"id":637},"内联代码与引用",[12,639,640,641,644],{},"行内代码 ",[36,642,643],{},"const re = \u002F\u003C(\\w+)>\u002Fg"," 里也有尖括号。",[12,646,647],{},"下面是一个引用块，里面同时含 HTML 和 shell：",[649,650,651,658],"blockquote",{},[12,652,653,654,657],{},"试试 ",[36,655,656],{},"echo \"\u003Chello> & goodbye\""," 这条命令。",[12,659,660],{},"它会输出带尖括号和与号的字符串，而 shell 会尝试把它当成重定向或子命令。",[19,662,663],{"id":663},"表格",[665,666,667,683],"table",{},[668,669,670],"thead",{},[671,672,673,677,680],"tr",{},[674,675,676],"th",{},"字符",[674,678,679],{},"XML 实体",[674,681,682],{},"是否必须",[684,685,686,700,713,727,742],"tbody",{},[671,687,688,693,697],{},[689,690,691],"td",{},[36,692,327],{},[689,694,695],{},[36,696,162],{},[689,698,699],{},"必须",[671,701,702,706,711],{},[689,703,704],{},[36,705,102],{},[689,707,708],{},[36,709,710],{},"&lt;",[689,712,699],{},[671,714,715,719,724],{},[689,716,717],{},[36,718,324],{},[689,720,721],{},[36,722,723],{},"&gt;",[689,725,726],{},"建议",[671,728,729,734,739],{},[689,730,731],{},[36,732,733],{},"\"",[689,735,736],{},[36,737,738],{},"&quot;",[689,740,741],{},"属性内必须",[671,743,744,749,754],{},[689,745,746],{},[36,747,748],{},"'",[689,750,751],{},[36,752,753],{},"&apos;",[689,755,756],{},"属性内建议",[19,758,759],{"id":759},"链接与图片",[12,761,762,763,321,768,321,772,776],{},"站内绝对链接：",[764,765,767],"a",{"href":766},"\u002Farchive","归档",[764,769,771],{"href":770},"\u002Ftags","标签",[764,773,775],{"href":774},"\u002Fabout","关于","。",[12,778,779],{},"相对链接在 feed 里必须被改写成基于站点的绝对链接，否则阅读器里点不动。",[12,781,782],{},"图片同理：",[12,784,785],{},[786,787],"img",{"alt":788,"src":789},"占位图","\u002Fimages\u002Fcovers\u002Fcharset.png",[19,791,792],{"id":792},"一段长文本",[12,794,795],{},"Lorem ipsum 不如直接说清楚：这一段就是用来测行高和段距的。中文排版里，1.75 的行高配合 17px 的字号，一行大约能放下 44 个汉字，这个宽度在多数屏幕上读起来是舒服的。再宽一点，眼睛回行会累；再窄一点，一句话要折三次，节奏就散了。",[12,797,798],{},"段落间距必须明显大于行距。我用的是 1.2em——看起来差别不大，但眯眼一看就知道哪里断开了。如果段落间距等于行距，整页会糊成一块灰，读者找不到停顿点，读三行就想走。",[19,800,801],{"id":801},"结尾",[12,803,804],{},"如果这一篇在 reader 里结构完好、代码块没跑版、图片能加载、链接可点，那 feed 就是对的。",[806,807,808],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#57606A;--shiki-dark:#9AA4B0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":74,"searchDepth":99,"depth":99,"links":810},[811,812,813,814,815,816,817],{"id":21,"depth":99,"text":21},{"id":67,"depth":99,"text":67},{"id":637,"depth":99,"text":637},{"id":663,"depth":99,"text":663},{"id":759,"depth":99,"text":759},{"id":792,"depth":99,"text":792},{"id":801,"depth":99,"text":801},"工程","2025-10-20","一篇专门用来喂 RSS\u002FAtom 校验器的文章。正文里塞满了 & \u003C > 引号、CDATA 边界、嵌套代码块和原始 HTML 片段。",false,"md",{},"\u002Fposts\u002F57060fdd",{"title":5,"description":820},"posts\u002Fxml-escaping-stress-test",[828,829,830],"RSS","XML","测试",null,"xfKfH3ykkR8LjuA_wTvL-JIYU8bpxjaZvZIWX7qeMxc",{"prev":834,"next":844},{"path":835,"title":836,"description":837,"date":838,"updated":831,"tags":839,"category":841,"cover":843,"draft":821,"pinned":389,"featured":821},"\u002Fposts\u002F90a855a1","排版比配色更决定气质","把同样一套颜色交给两个人排，出来的东西可以完全不同。这篇讲我怎么用字重、行距和留白，在不换颜色的前提下改变一个页面的脾气。","2025-10-28",[840,841,842],"排版","设计","CSS","\u002Fimages\u002Fcovers\u002Ftypography.png",{"path":845,"title":846,"description":847,"date":848,"updated":831,"tags":849,"category":818,"cover":851,"draft":821,"pinned":821,"featured":389},"\u002Fposts\u002Ff50285d4","测试文章甲：带封面的推荐位文章","这是首页改版验收用的测试文章甲，有封面且标记为 featured，应出现在顶部轮播第一位。","2026-11-01",[830,850],"首页改版","\u002Fimages\u002Fcovers\u002Fview-transition.png",[853,855,862],{"path":845,"title":846,"description":847,"date":848,"updated":831,"tags":854,"category":818,"cover":851,"draft":821,"pinned":821,"featured":389},[830,850],{"path":856,"title":857,"description":858,"date":859,"updated":831,"tags":860,"category":818,"cover":861,"draft":821,"pinned":821,"featured":821},"\u002Fposts\u002F97844eaa","测试文章戊：外链封面","这是首页改版验收用的测试文章戊，封面使用外链 URL，验证 cover 字段支持外链图片。","2026-09-15",[830],"https:\u002F\u002Fpicsum.photos\u002Fseed\u002Fkemiao-test\u002F800\u002F450",{"path":863,"title":864,"description":865,"date":866,"updated":831,"tags":867,"category":818,"cover":831,"draft":821,"pinned":821,"featured":821},"\u002Fposts\u002F63afeff4","我删掉了站点的 90% 功能","一个博客需要什么？列表、文章、归档、搜索、订阅。其他东西我全删了，然后发现加载时间从 2.3 秒掉到了 380 毫秒。","2025-10-12",[868,869,870],"性能","架构","反思",1794495069899]