| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- /**
- * 问卷内容(`<question-cards>…</question-cards>` 行)的**解析**——纯函数,可被 harness 直接测。
- *
- * 为什么单独抽出来(2026-09-24):渲染层交给 `QuestionCard` 的 `content` 是
- * **带标记的整段**(切行器 `StreamXMLFilter` 给 block 行产出的就是 `openTag + inner + closeTag`,
- * 见 `handleBehavior`)。而组件的两个解析函数一个剥标记、一个不剥 ——
- * 于是"作答后写回 `{answers:…}`"时只读分支解析失败、交互分支也没有 `questions`,
- * **整张卡片渲染成空**(用户报「提交完问卷直接看不见了」)。
- * 现在两处都走这里的同一个解包逻辑,并且 harness 会用**真实切行器**跑一遍往返。
- */
- export interface QuestionOption {
- label: string;
- description?: string;
- recommended?: boolean;
- [key: string]: unknown;
- }
- export interface Question {
- header: string;
- question: string;
- multiSelect?: boolean;
- allowFreeformInput?: boolean;
- message?: string;
- /** 输入框常驻并排在最上(用于补充信息),默认 false 保持原有"其他"行为 */
- freeformInputOnTop?: boolean;
- /** 上述常驻输入框的 placeholder */
- freeformPlaceholder?: string;
- options?: QuestionOption[];
- }
- /** 问卷答案(`QuestionCard` 提交时给出的形状,也是只读渲染读的形状) */
- export interface QuestionCardAnswer {
- header?: string;
- selected?: string[];
- freeText?: string;
- skipped?: boolean;
- }
- const MARKER_RE = /<question-cards\b[^>]*>([\s\S]*?)<\/question-cards>/i;
- /**
- * 取出标记内部的 JSON 文本。
- *
- * - 内容带 `<question-cards>` 标记 → 取标记内部(两处解析都靠它)
- * - 不带标记 → 原样返回(兼容直接传 JSON 的调用方)
- */
- export const unwrapQuestionCardsContent = (content: string): string => {
- const text = String(content ?? "").trim();
- const match = text.match(MARKER_RE);
- return (match ? match[1] : text).trim();
- };
- const safeParse = (text: string): any => {
- try {
- return JSON.parse(text);
- } catch {
- return null;
- }
- };
- /** 解析问卷题目(交互态渲染用);解析不出来返回空数组 */
- export const parseQuestionsFromContent = (content: string): Question[] => {
- const parsed = safeParse(unwrapQuestionCardsContent(content));
- const questions = parsed?.questions;
- return Array.isArray(questions) ? (questions as Question[]) : [];
- };
- /** 解析已作答结果(只读态渲染用);没有答案返回 null */
- export const parseAnswersFromContent = (content: string): QuestionCardAnswer[] | null => {
- const parsed = safeParse(unwrapQuestionCardsContent(content));
- const answers = parsed?.answers;
- return Array.isArray(answers) ? (answers as QuestionCardAnswer[]) : null;
- };
|