訂明本例驗收任務
假設原始答案流用整行 @@Q:ID@@ 開答案,@@END@@ 關答案;編號只用英文字母、數字及連字號。行首反斜線會去掉首字,餘下原樣當內容,即使像標記也一樣。框外非空字留作未配對。嵌套開框、重複已完成編號、無開框的結束、錯標記或未閉框都停解析,保留已完成及未閉內容;不按輸出次序配題。
- 解析整行開閉標記
- 轉義行原樣留作內容
- 保留錯格式及未配對內容
解析錯誤保留已完成答案及現未閉框,須修原始答案流。
使用已填個案
| 個案 | 已提供輸入 | 預期結構化結果 | 判斷及原因 |
|---|---|---|---|
| Q2 答案先於 Q1 | {"raw":"@@Q:Q2@@\nsecond\n@@END@@\n@@Q:Q1@@\nfirst\n@@END@@"} | {"answers":[{"id":"Q2","text":"second"},{"id":"Q1","text":"first"}],"unassigned":[],"open":null,"error":null,"ready":true} | 按整行標記配答案編號,不按先後位置。 |
| 轉義標記仍是內容 | {"raw":"@@Q:Q1@@\n\\@@Q:FAKE@@\n@@END@@"} | {"answers":[{"id":"Q1","text":"@@Q:FAKE@@"}],"unassigned":[],"open":null,"error":null,"ready":true} | 行首轉義令看似 FAKE 開框留作答案原文。 |
| 未閉答案內又開框 | {"raw":"@@Q:Q1@@\n@@Q:Q2@@"} | {"answers":[],"unassigned":[],"open":{"id":"Q1","lines":[]},"error":{"code":"nested-start","line":2},"ready":false} | 第二個開框不能默默丟掉 Q1,保留未閉狀態及錯行。 |
| 答案框外文字 | {"raw":"Intro\n@@Q:Q1@@\nanswer\n@@END@@"} | {"answers":[{"id":"Q1","text":"answer"}],"unassigned":["Intro"],"open":null,"error":null,"ready":false} | Q1 框有效並保留,Intro 未配對,不能列全部已備好。 |
| 答案欠結束標記 | {"raw":"@@Q:Q1@@\nanswer"} | {"answers":[],"unassigned":[],"open":{"id":"Q1","lines":["answer"]},"error":{"code":"unclosed-block","line":2},"ready":false} | 按已訂原始格式,這段答案尚未完整。 |
| 編號內有空格 | {"raw":"@@Q:Q 1@@"} | {"answers":[],"unassigned":[],"open":null,"error":{"code":"malformed-marker","line":1},"ready":false} | 像標記但編號無效的行,不當普通資料文字。 |
逐步處理 malformed-id-marker 個案,須得到甚麼結果?原因是甚麼?
查看參考答案
{"answers":[],"unassigned":[],"open":null,"error":{"code":"malformed-marker","line":1},"ready":false}。像標記但編號無效的行,不當普通資料文字。
執行完整本機驗算
import assert from 'node:assert/strict';
function evaluate(x){
const lines=x.raw.split('\n'),answers=[],unassigned=[];let open=null;
const fail=(code,line)=>({answers,unassigned,open,error:{code,line},ready:false});
const literal=text=>{if(open)open.lines.push(text);else if(text!=='')unassigned.push(text);};
for(let i=0;i<lines.length;i++){const line=lines[i];if(line.charCodeAt(0)===92){literal(line.slice(1));continue;}
const m=/^@@Q:([A-Za-z0-9-]+)@@$/.exec(line);if(m){if(open)return fail('nested-start',i+1);if(answers.some(a=>a.id===m[1]))return fail('duplicate-id',i+1);open={id:m[1],lines:[]};}
else if(line==='@@END@@'){if(!open)return fail('unmatched-end',i+1);answers.push({id:open.id,text:open.lines.join('\n')});open=null;}
else if(line.startsWith('@@'))return fail('malformed-marker',i+1);else literal(line);
}
if(open)return fail('unclosed-block',lines.length);return {answers,unassigned,open:null,error:null,ready:unassigned.length===0};
}
const cases = [
{
"id": "reverse-answer-order",
"input": {
"raw": "@@Q:Q2@@\nsecond\n@@END@@\n@@Q:Q1@@\nfirst\n@@END@@"
},
"expected": {
"answers": [
{
"id": "Q2",
"text": "second"
},
{
"id": "Q1",
"text": "first"
}
],
"unassigned": [],
"open": null,
"error": null,
"ready": true
}
},
{
"id": "escaped-literal-marker",
"input": {
"raw": "@@Q:Q1@@\n\\@@Q:FAKE@@\n@@END@@"
},
"expected": {
"answers": [
{
"id": "Q1",
"text": "@@Q:FAKE@@"
}
],
"unassigned": [],
"open": null,
"error": null,
"ready": true
}
},
{
"id": "nested-start",
"input": {
"raw": "@@Q:Q1@@\n@@Q:Q2@@"
},
"expected": {
"answers": [],
"unassigned": [],
"open": {
"id": "Q1",
"lines": []
},
"error": {
"code": "nested-start",
"line": 2
},
"ready": false
}
},
{
"id": "unassigned-preamble",
"input": {
"raw": "Intro\n@@Q:Q1@@\nanswer\n@@END@@"
},
"expected": {
"answers": [
{
"id": "Q1",
"text": "answer"
}
],
"unassigned": [
"Intro"
],
"open": null,
"error": null,
"ready": false
}
},
{
"id": "unclosed-answer",
"input": {
"raw": "@@Q:Q1@@\nanswer"
},
"expected": {
"answers": [],
"unassigned": [],
"open": {
"id": "Q1",
"lines": [
"answer"
]
},
"error": {
"code": "unclosed-block",
"line": 2
},
"ready": false
}
},
{
"id": "malformed-id-marker",
"input": {
"raw": "@@Q:Q 1@@"
},
"expected": {
"answers": [],
"unassigned": [],
"open": null,
"error": {
"code": "malformed-marker",
"line": 1
},
"ready": false
}
}
];
for (const c of cases) assert.deepEqual(evaluate(c.input), c.expected, c.id);
console.log(JSON.stringify({checked:cases.length,caseIds:cases.map(c=>c.id)}));
把此格式連批次驗收表保存。這是自訂本機分隔規則,不是供應商格式;明訂 LF 換行,換行或轉義規則改動須另加個案。