表达式脚本 - 简介
From LimeSurvey Manual
简介
LimeSurvey 使用表达式脚本(Expression Script),使 LimeSurvey 能够支持更复杂的分支、评估、验证和个性化定制。它在后端取代了 LimeSurvey 原有的替换、条件和评估的管理方式。由于几乎消除了运行时的数据库读取,它还大大加快了处理速度。表达式脚本由 Thomas White 博士(TMSWhite)开发。
关键定义
- 表达式:任何被花括号括起来的内容:
- 前提是左花括号之后或右花括号之前没有紧跟空白字符。
- 表达式的内容由 EM 计算,因此可以包含数学公式、函数以及复杂的字符串和日期处理。
- 个性化定制(Tailoring):有时也称为“管道传递(piping)”。这是有条件地修改文本的过程:
- 您可以访问所有“替换字段”、参与者数据和反馈数据。
- 您还可以更轻松地访问问题、答案及其属性。
- 相关表达式:一个控制问题可见性的新问题属性:
- 如果设置了相关表达式,则只有当该表达式的计算结果为真时,才会显示该问题。
- 在内部,所有 array_filter 和 array_filter_exclude 命令都会变成子问题级别的关联条件。
- SGQA(过去变量的命名方式):
- 代表 Survey-Group-Question-Answer(调查-题组-问题-答案)
- SGQA 变量名形如 123X5X382X971,并且可能带有子问题后缀。
- 这些变量名特定于底层的 S/Q/G/A 数据库代码,因此经常需要修改
- 方程式问题类型:一种新的问题类型,可将计算结果或报告保存到数据库:
- 它类似于文本显示问题,但即使您设置了“始终隐藏本题”,其内容也会保存到数据库。
- 问题编码:这是 EM 首选的变量名:
- 它可以是说明问题用途的描述性名称,使复杂的逻辑更易阅读。
- 有效的问题编码不应以数字开头,因此在使用问题编码为问题编号时,只需使用“q1”、“q1a”或“g1q2”即可。
- 如果您将数据导出到 SPSS 或 R,它将成为变量名。因此,如果您要进行统计分析,只需创建唯一的问题编码。
我必须使用表达式脚本吗?
简短的回答是“不必”。不过,这在很大程度上取决于您想要创建的调查的复杂程度。
例如,条件编辑器涵盖了一些可应用于调查问题的基本表达式。但是,条件编辑器的功能有限。这就是使用表达式脚本的原因——它扩展了自定义的可能性。
我可以混合使用条件和相关表达式吗?
可以。您可以对某些问题使用条件编辑器,对其他问题使用相关表达式。
您不能在同一个问题中同时设置条件和表达式!一旦设置了条件,它将替换相关表达式字段中写入的任何表达式。此外,相关表达式字段也不能再手动编辑。
不过,仍有办法在一个问题中同时使用表达式和条件。如上所述,条件会替换相关表达式字段。设置完成后,请查看新生成的表达式并将其复制到文本编辑器中。然后从条件编辑器中删除新创建的条件,再编辑该问题,将文本编辑器文件中基于条件的表达式与您希望使用的其他表达式一起添加进去。
我应该如何在条件和关联之间做出选择?
以下是两种方式的优缺点列表:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 条件 | 1. 提供便于创建简单条件的图形界面。 2. 图形界面文档完善,支持团队也很熟悉 |
1. 仅支持简单的比较,对“与”和“或”条件的处理不佳。 2. 级联条件的运行不稳定 3. 速度慢,需要大量访问数据库,可能会拖慢较长的问卷。 4. 有重新加载条件时出现问题的报告。 5. 当有几十、几百甚至几千个问题时,图形界面的扩展性不佳。 6. 由于必须使用 SGQA 名称,转换纸质问卷可能会很慢。 7. 复杂分支所需的逻辑通常需要程序员自定义编写代码。 |
| 相关性 | 1. 支持非常复杂的逻辑,包括 80 多个函数以及数学/字符串运算符。 2. 完美支持级联逻辑。 3. 速度快,无需额外的数据库调用,可支持 1000 个以上问题的问卷。 4. 由于不需要 SGQA 代码,重新加载逻辑时不会出现问题。 5. 语法高亮可适用于 1000 个以上问题的问卷。 6. 对于希望将现有纸质问卷电子化的团队,使用简便快捷。 7. 无需程序员即可轻松支持半结构化访谈和流行病学调查。 |
1. 没有用于简单条件的图形界面,而是使用语法高亮。 |
- 我们建议您选择更符合自身需求的方式。
- 有关 EM 功能的更详细说明,请点击以下链接。
入门
开始使用 EM 的最佳方式是:
- 从 https://www.limesurvey.org/en/download 安装最新的稳定版本。
- 导入并探索一些示例调查。
- 浏览使用案例和操作指南以及分步示例。
- 阅读 EM 文档(本页面)
- 独立表达式的单元测试(高级)
- 展示了使用所有 EM 函数和运算符的示例,以及 PHP 和 JavaScript 的运行结果;
- 请注意,有少数函数在 PHP 和 JavaScript 版本中会产生不同的结果,因此本页面可以帮助您相应地规划 EM 逻辑。
- 独立表达式的单元测试(高级)
术语
以下词语常用于描述 EM 的功能:
- 基于关联的分支 - 如果某个问题是相关的,则提出该问题,否则不提出(例如,将其设为不可见,并在数据库中标记为 NULL)。您可以在问题编辑面板以及题组编辑面板中找到关联字段。后者用于将一组条件应用于整个题组,而无需将相同的条件复制到每个问题,和/或结合题组级别与问题级别的条件逻辑。
- 个性化定制(Tailoring) - 一旦确定了应该提出哪些问题,个性化定制(有时称为管道传递)就会指定如何提出问题。这不仅支持简单的替换(如 {TOKEN:FIRSTNAME}),还支持根据对象的性别或数量进行动词变位和名词变格。它还允许您根据调查受访者是否回答了其他问题(或如何回答)来更改向其传递的信息。
- 方程式 - EM 增加了一种名为方程式的新问题类型,用于存储表达式的结果。即使您在页面上隐藏了方程式,其结果仍会被计算并写入数据库。因此,它们可用于隐藏的评分计算、基于复杂方程式的导航、评估,以及将在数据库中生成并存储的报告。
关联与级联关联
现在每种问题类型都有一个关联选项,用于控制是否显示该问题。EM 会按照相关表达式在调查中出现的顺序依次处理。如果表达式为真(或缺失——以支持旧版调查),则会显示该问题。如果不相关,则该问题将被隐藏,并且其值在数据库中将被置为 NULL。如果某个题组中没有相关的问题,则整个题组将被跳过。
此外,如果表达式中的任何变量不相关,则该表达式的计算结果始终为假。这就实现了级联关联,因此您无需为每个问题编写很长的相关表达式。
假设您有五个问题 Q1-Q5,并且您只想在 Q1 已回答时显示 Q2,在 Q2 已回答时显示 Q3,依此类推。相关表达式可以如下:
| 问题编码 | 关联 | 问题 |
|---|---|---|
| Q1 | 1 | 您叫什么名字? |
| Q2 | Q1 | {Q1},您多大了? |
| Q3 | Q2 | 那么,您今年 {Q2} 岁。您结婚了吗? |
| Q4 | Q3 == "Y" | {Q1},您结婚多久了? |
| Q5 | Q4 | {Q1},您有几个孩子? |
题组级别的关联
ExpressionScript 还支持题组级别的关联,这使得实现循环更加容易。假设您想收集最多 10 个对象的信息(例如产品或一个家庭中的成员),您首先要确定有多少个对象需要后续跟进(例如询问一个家庭中有多少人居住,或让人们从一个长列表中勾选他们喜欢的产品)。在得知有多少个对象需要跟进后,您可以为 10 个后续问题题组分别使用题组级别的关联,如 {count >= 1}、{count >=2}、……{count >= 10}。在每个题组内,您可以设置问题级别的条件逻辑(例如,针对每个对象的性别或年龄的特定后续问题)。问题级别和题组级别的相关表达式会进行“与”运算,以确定应显示哪些内容。
要查看此类示例,请导入以下调查:人口普查调查示例。
从下面的屏幕截图可以看出,如果受访者与至少另一位同住者一起生活,则会显示(或关联)Person 1 题组:

定制/管道传递
现在,花括号内的任何内容都被视为表达式(下文所述的一种例外情况除外)。表达式可以访问所有 LimeReplacementFields 和变量(通过多个别名)、所有典型的运算符(数学、逻辑和比较运算符),以及数十个函数(这些函数甚至可以在客户端动态运行)。
通过使用这些方程式,您可以完成以下操作:
- 根据之前的反馈,有条件地向受访者显示定制的消息;
- 创建评估并显示评估结果(或根据这些结果有条件地跳转或显示消息),而无需使用评估模块本身;
- 在问题、答案和报告中进行动词变位和名词变格;
- 在调查结束时的“显示您的答案”页面之前显示反馈摘要。
方程式
有一种新的问题类型,称为方程式。您可以将其视为文本显示问题类型,区别在于它会将所显示的内容的值存储到数据库中。因此,如果方程式问题文本包含评估计算,该值将存储在数据库的一个变量中,并可在公开或私有统计中显示。
语法
现在,花括号内包含的任何内容都被视为表达式(有一个例外:开头和结尾不能有空白字符,这是为了确保 ExpressionScript 不会尝试处理嵌入的 JavaScript)。
请注意,表达式可以跨多行,只要左花括号之后或右花括号之前没有空白字符即可。这对于嵌套的“if()”语句尤其有用,例如:
{if(is_empty(PFTotals),
'',
if(PFTotals >= -5 && PFTotals <= -4,
'Very Soft',
if(PFTotals >= -3 && PFTotals <= -2,
'Soft',
if(PFTotals == -1,
'Somewhat Soft',
if(PFTotals == 0,
'Moderate',
if(PFTotals == 1,
'Somewhat Hard',
if(PFTotals >= 2 && PFTotals <= 3,
'Hard',
if(PFTotals >= 4 && PFTotals <= 5,
'Very Hard',
''
)
)
)
)
)
)
)
)}
ExpressionScript 支持以下语法:
- 所有标准数学运算符(例如 +,-,*,/,!);
- 所有标准比较运算符(例如 <,<=,==,!=,>,>=,以及它们的等效写法:lt、le、eq、ne、gt、ge);
- 圆括号(用于对子表达式进行分组);
- 条件运算符(例如 &&,| | 以及它们的等效写法:and、or);
- 单引号和双引号字符串(两者可以互相嵌套另一种引号的字符串);
- 逗号运算符(可以包含一组表达式,并仅返回最后的结果);
- 赋值运算符(=);
- 预定义变量(用于引用问题、问题属性和反馈),例如 SGQA 代码;
- 预定义函数(已有 80 多个,并且很容易添加更多)。
运算符
EM 语法遵循常规的运算符优先级:
| 级别 | 运算符 | 描述 |
|---|---|---|
| 1 | () | 用于分组或调用函数的圆括号 |
| 2 | ! - + | 一元运算符:非、取负、一元加 |
| 3 | * / | 乘、除 |
| 4 | + - | 加、减 |
| 5 | < <= > >= lt le gt ge | 大小比较 |
| 6 | == != eq ne | 相等比较 |
| 7 | and | 逻辑与 |
| 8 | or | 逻辑或 |
| 9 | = | 赋值运算符 |
| 10 | , | 逗号运算符 |
关于数字与字符串不匹配以及字母/数字比较的警告
使用大小比较或相等比较来比较值时,请注意类型不匹配的情况。用户输入的值(或通过答案代码提供的值)在明确是数字时可以作为数字使用。
如果您用 " 将其中一个值括起来,则会进行文本/字符串比较。如果您希望按数字比较,切勿用 " 括住数字。
例如,如果 Q0.NAOK 是值为 9 的数字问题,则 Q0.NAOK > "50" 为真。这是因为运算符 > 会将其视为字母顺序比较,而不是数值比较。
为确保比较的是整数值,您可以使用 intval(Q0.NAOK) > 50。请记住,如果 Q0.NAOK 不是数字(为空或是字符串),则 intval(Q0.NAOK) === 0。要比较字符串值("A" < "B"),请直接使用 strcmp:strcmp(Q0.NAOK,"B") 或 strcmp(Q0.NAOK,"A5")。
使用赋值运算符 (=) 的注意事项
除非绝对必要,否则应避免使用赋值运算符,因为它们可能会导致意外的副作用。例如,如果您更改了之前某个反馈的值,该问题与当前问题之间的级联相关性和验证逻辑不会被重新计算,因此最终可能会得到内部不一致的数据(例如,本应被置为 NULL 却仍保持已回答状态的问题,或者被跳过但本应回答的问题)。通常,如果您想为变量赋值,应创建一个方程式问题类型,并使用表达式来设置其值。不过,在极少数情况下人们确实需要此运算符,因此我们保留了它。
为了提醒您注意此运算符,它在语法方程式中以红色字体显示(以免与“==”混淆)。
使用赋值运算符
您可能需要使用赋值运算符的主要原因有:
- 您需要通过方程式为不接受默认值的问题设置默认值(例如单选列表,其用户界面允许您选择其中一个答案选项,但不允许输入方程式)。不过请小心,因为 LimeSurvey 无法验证您的方程式生成的结果是否为该问题的允许答案之一;
- 您需要根据后面的反馈强制更改之前某个问题的反馈;
- 等等……
您可以使用整个表达式管理器系统来实现此目的。最好为此使用方程式。
一些示例:
- 将简短文本问题的答案设置为小写:
{QCODE=strtolower(QCODE.NAOK)}; - 在调查开始时为阵列问题类型设置默认答案:
{Q1_SQ1=(is_empty(Q1_SQ1.NAOK),"A99",Q1_SQ1.NAOK)}; - 在调查开始时为阵列文本问题类型设置默认答案:
{Q1_SQY1_SQX1 = (is_empty(Q1_SQY1_SQX1.NAOK),"Inserted answer", Q1_SQY1_SQX1.NAOK)}; - 按条件设置答案:
{QCODE=if(YesNo="Y","A1","")}。
XSS 安全
启用 XSS 后,表达式管理器系统的某些部分将无法使用:
- 在一个表达式中开始 HTML 标签,却在另一个表达式中结束;
- 在 URL 中使用复杂表达式。
示例和解决方法:
{if( 1 ,"<strong>","")}information{if( 1 ,"</strong>","")}在启用 XSS 安全时无法使用,此时您可以使用{if(1,"<strong>information</strong>","information")};<a href="/script.php?value={if(QCODE == "Y","yes","no")}">next</a>,此时您可以使用方程式问题,因为使用完整的问题代码是可以的:<a href="/script.php?value={EQUATION.NAOK}">next</a>。
访问变量
ExpressionScript 为您可能需要的任何变量提供只读访问。为了向后兼容,它提供对以下内容的访问:
- TOKEN:xxx - 邀請碼的值(例如 TOKEN:FIRSTNAME、TOKEN:ATTRIBUTE_5)(仅适用于非匿名调查)。
- 所有模板使用的 {XXX} 值。
- 在问题文本中,您可以使用 {QID}(将被替换为问题 ID)和 {SGQ}(将被替换为该问题的 SGQA)。
- {SAVEDID} 表示当前的反馈ID
此外,ExpressionScript 允许您通过问题代码(数据库中问题表的“title”列)来引用变量。当您将数据导出到 SPSS、R 或 SAS 时,这也是所使用的变量标签。例如,如果您有关于姓名、年龄和性别的问题,您可以将这些变量称为 name、age 和 gender,而不是 12345X13X22、12345X13X23 和 12345X13X24。这使得每个人都更容易阅读方程式并验证逻辑,同时也使您无需记录组或问题编号即可随意调整问题顺序。
重要提示: 引用出现在前面页面或问题中的变量更为安全。
此外,ExpressionScript 还允许您访问问题的许多属性:
| 语法 | 含义 | 示例 | 示例结果 |
|---|---|---|---|
| Qcode | Qcode.code 的别名 | {implode(',',name,gender)} | 'Tom','M' |
| Qcode.code | 如果问题相关,则为该问题所选的反馈代码(否则为空);如果不是带代码的问题,则为文本值 | {implode(',',name.code,gender.code)} | 'Tom','M' |
| Qcode.NAOK | 与 Qcode 相同 - 请参阅关于 NAOK 的讨论 | {gender.NAOK} | 'M' |
| Qcode.value | 如果问题相关,则为该问题的评估值(否则为空);如果不是带代码的问题,则为文本值 | {gender.value} | '1' |
| Qcode.valueNAOK | 与 Qcode.value 相同 - 请参阅关于 NAOK 的讨论 | {gender.valueNAOK} | '1' |
| Qcode.shown | 问题的显示值 | {implode(',',name.shown,gender.shown)} | 'Tom','Male' |
| Qcode.question | 问题的文本 | {gender.question} | 'What is your gender?' |
| Qcode.mandatory | 问题是否为必答题 (Y/N) | {gender.mandatory} | 'N' |
| Qcode.qid | 内部问题编号(不是顺序编号) | {gender.qid} | 337 |
| Qcode.type | 问题类型 | {gender.type} | 'G' |
| Qcode.jsName | 该问题正确的 JavaScript 名称,无论其是否在本页声明 | {gender.jsName} | 'java1827X3X337' |
| Qcode.gid | 内部组编号(不是顺序编号) | {gender.gid} | 3 |
| Qcode.qseq | 问题的顺序编号,从 0 开始 | {gender.qseq} | 5 |
| Qcode.gseq | 组的顺序编号,从 0 开始 | {gender.gseq} | 1 |
| Qcode.relevanceStatus | 问题当前是否相关(0 或 1) | {gender.relevanceStatus} | 1 |
| Qcode.relevance | 问题级相关表达式 | {gender.relevance} | '!is_empty(name)' |
| Qcode.grelevance | 组级相关表达式 | {gender.grelevance} | 'num_children >= 5' |
| Qcode.sgqa | 该问题的 SGQA 值 | {gender.sgqa} | '1827X3X337' |
HTML编辑器问题
如果您使用 HTML编辑器,某些字符会被替换为 HTML 实体:
- & 被替换为 &
- < 被替换为 <
- > 被替换为 >
如果您使用 HTML编辑器,则需要使用:
- and 代替 &
- lt 代替 <
- le 代替 <=
- gt 代替 >
- ge 代替 >=
建议清除表达式中出现的 HTML。如果您使用 LimeSurvey 的 HTML编辑器,请单击“源代码”按钮(位于编辑器的左上方),并删除所有与表达式无关的字符(例如
、
等)。
Qcode 变量命名
以下是按问题类型构造 Qcode(以及访问某些属性)的详细说明。一般情况下,Qcode 的构造方式如下:
QuestionCode . '_' . SubQuestionID . '_' . ScaleId
对于评论和其它,相应的问题代码分别为 QuestionCode_comment 和 QuestionCode_other。
| 类型 | 描述 | 代码 | 子问题 | 答案项 | 尺度 | 答案代码 | 显示的答案 | 相关性 |
|---|---|---|---|---|---|---|---|---|
| 5 | 5分制选择题 单选按钮 | Q1 | 1-5 | {Q1} | {Q1.shown} | {Q1==3} | ||
| B | 阵列(10分制选择题)单选按钮 | Q2 | L1-L6 | 1-10 | {Q2_L2} | {Q2_L2.shown} | {Q2_L2==7} | |
| A | 阵列(5分制选择题)单选按钮 | Q3 | 1-5 | 1-5 | {Q3_1} | {Q3_1.shown} | {Q3_1>=3} | |
| 1 | 阵列(灵活标签)双尺度 | Q4 | sq1-sq5 | 0:a1-a3 | 1:b1-b3 | {Q4_sq1_0} | {Q4_sq1_1.shown} | {Q4_sq1_1=='b2'} |
| H | 阵列(灵活)- 列格式 | Q5 | 1-5 | s,m,t | {Q5_1} | {Q5_1.shown} | {Q5_1=='s'} | |
| F | 阵列(灵活)- 行格式 | Q6 | F1-F5 | 1-5 | {Q6_F3} | {Q6_F3.shown} | {Q6_F3==4} | |
| E | 阵列 (增加,不变,减少) 单选按钮 | Q7 | 1-7 | I,S,D | {Q7_4} | {Q7_4.shown} | {Q7_4=='D'} | |
| : | 阵列(多重灵活)1 到 10 | Q8 | ls1,todo,ls2 | min,max,avg | {Q8_ls1_max} | {Q8_ls2_avg.shown} | {Q8_ls2_min==7} | |
| ; | 阵列(多重灵活)文本 | Q9 | hp,st,sw | 1st,2nd,3rd | {Q9_hp_3rd} | {Q9_hp_3rd.shown} | {Q9_hp_3rd=='Peter'} | |
| C | 阵列(是/不确定/否)单选按钮 | Q10 | 1-5 | Y,N,U | {Q10_1} | {Q10_1.shown} | {Q10_3=='Y'} | |
| X | 文本显示问题 | Q11 | {Q11.shown} | |||||
| D | 日期 | Q12 | {Q12} | {Q12.shown} | ||||
| * | 方程式 | Q13 | {Q13} | {Q13.shown} | {Q13>5} | |||
| ~124~ | 文件上传(记录上传的文件数量) | Q14 | {Q14} | {Q14>0} | ||||
| G | 性別 下拉列表 | Q15 | M,F | {Q15} | {Q15.shown} | {Q15=='M'} | ||
| U | 巨大自由文本 | Q16 | {Q16} | {Q16.shown} | {strlen(Q16)>100} | |||
| I | 语言问题 | Q17 | {Q17} | {Q17.shown} | {Q17=='en'} | |||
| ! | 列表 - 下拉列表 | Q18 | 1-5 | {Q18} | {Q18.shown} | {Q18==3} | ||
| L | 列表 下拉列表/单选按钮列表 | Q19 | A-Z | {Q19} | {Q19.shown} | {Q19=='X'} | ||
| O | 带评论的列表 下拉列表/单选按钮列表 + 文本区域 | Q20 | A-F | {Q20},{Q20comment} | {Q20.shown} | {Q20=='B'} | ||
| T | 长自由文本 | Q21 | {Q21} | {Q21.shown} | {strstr(Q21,'hello')>0} | |||
| M | 多选题 复选框 | Q22 | A-F, other | {Q22_E}, {Q22_other} | {Q22_E.shown}, {Q22_other.shown} | {Q22_E=='Y'} | ||
| P | 带评论的多选题 复选框 + 文本 | Q23 | A-F | {Q23_D}, {Q23_Dcomment} | {Q23_D.shown} | {!is_empty(Q23)} | ||
| K | 多数值问题 | Q24 | self,mom,dad | {Q24_self} | {Q24_self.shown} | {Q24_self>30} | ||
| Q | 多短文本 | Q25 | A-F | {Q25_B} | {Q25_B.shown} | {substr(Q25_B,1,1)=='Q'} | ||
| N | 数值问题类型 | Q26 | {Q26} | {Q26.shown} | {Q26 > 30} | |||
| R | 排序样式 | Q27 | 1-4 | {Q27_1} | {Q27_1.shown} | {Q27_1==3} | ||
| S | 短自由文本 | Q28 | {Q28} | {Q28.shown} | {Q28=='mine'} | |||
| Y | 是/否 单选按钮 | Q29 | {Q29} | {Q29.shown} | {Q29=='Y'} |
NAOK 的用法
NAOK --> “Not Applicable”(NA,不适用)是可以接受的(OK)
使用 NAOK 表示所有或部分变量不相关(例如“Not Applicable”(NA,不适用)是可以接受的(OK))。
例如:如果 Q1 的某个子问题被过滤,count(Q1_SQ1,Q1_SQ2,Q1_SQ3,Q1_SQ4) 将始终返回空字符串。 要统计此类问题中被选中的子问题数量,可以使用 count(Q1_SQ1.NAOK,Q1_SQ2.NAOK,Q1_SQ3.NAOK,Q1_SQ4.NAOK)。如果子问题被隐藏,EM 将返回空字符串。
如果不使用 NAOK,当某个问题或子问题被隐藏时,EM 将始终返回空字符串(等同于返回 false)。
.shown 始终使用 NAOK 机制(隐藏时返回空字符串),但如果您需要答案代码:在问题代码后添加 .NAOK 始终是个好主意(除非您确实需要不加并且清楚自己在做什么)。
更多信息请参阅覆盖级联条件小节。
保留变量“this”、“self”和“that”
您经常需要评估问题的所有部分,例如统计已回答的子问题数量或对分数求和。有时您只想处理问题的某些行或列(例如获取行或列的总和并将其存储在数据库中)。这些保留变量使这一过程相对轻松。
“This”变量
“this”变量仅用于“整个问题验证公式”和“子问题验证公式”选项(后者无法通过图形界面设置)。它会展开为这些问题中每个单元格的变量名。因此,如果您想确保每个条目都大于三,可以将“子问题验证公式”设置为 (this > 3)。
“Self”变量
“self”和“that”变量功能更强大,它们充当宏,在处理方程式之前被展开。“self”变量的语法选项如下:
- self
- self.suffix
- self.sub-selector
- self.sub-selector.suffix
- suffix(后缀)是任意常规的 qcode 后缀(例如 NAOK、value、shown)
- sub-selector(子选择器)可以是以下之一:
- comments - 仅选择属于评论的子问题(例如带评论的多选题和带评论的列表);
- nocomments - 仅选择不属于评论的子问题;
- sq_X - 其中 X 为行或列标识符。仅选择与模式 X 匹配的子问题。请注意,搜索是针对完整的代码标识符进行的,然后 sq_X 会匹配并包含子问题 nX、X、Xn(例如,如果使用 sq_1,则会包含子问题 a1、1a、1、11 或 001)。请注意双尺度问题类型,其子问题代码为 QCODE_SQCODE_1 和 QCODE_SQCODE_1;还有排序问题类型,其子问题代码为 QCODE_1、QCODE_2 ……。
示例:
- 问题的任何部分是否已被回答?-> {count(self.NAOK)>0}
- 此问题的评估得分是多少?-> {sum(self.value)}
您还可以使用它们来获取行合计和列合计。假设您有一个数字阵列,行为 A-E,列为 1-5。
- 累计是多少?-> {sum(self.NAOK)}
- B 行的合计是多少?-> {sum(self.sq_B.NAOK)}
- 第 3 列的合计是多少?-> {sum(self.sq_3.NAOK)}
“That”变量
“that”变量类似于“self”变量,但它允许您引用其他问题。其语法为:
- that.qname
- that.qname.suffix
- that.qname.sub-selector
- that.qname.sub-selector.suffix
qname 是不带任何子问题扩展的问题名称。例如,我们创建一个问题“q1”,其中“q”同样代表其 qname。
示例:
- 问题 q1 的任何部分是否已被回答?-> {count(that.q1.NAOK)>0}
- q2 的评估得分是多少?-> {sum(that.q2.NAOK)}
- q3 的累计是多少?-> {sum(that.q3.NAOK)}
- q4 中 C 行的合计是多少?-> {sum(that.q4.sq_C.NAOK)}
- q4 中第 2 列的合计是多少?-> {sum(that.q4.sq_2.NAOK)}
“self”和“that”变量可用于任何相关性、验证或定制中。
需要注意的一点是,当您使用显示逻辑文件功能时,它会显示“self”和“that”展开后的值。这样您(以及 EM)就可以看到将要生成的实际方程式,并验证这些变量是否存在。由于可能会看到相当冗长的方程式,这看起来可能令人困惑。不过,如果您编辑该问题,将会看到使用“self”和/或“that”的原始方程式。
函数的使用
ExpressionScript 提供数学函数、字符串函数和用户自定义函数,如下所示。这些函数都有对应的 PHP 和 JavaScript 实现,因此在服务器端(PHP)和客户端(JavaScript)的运行效果完全一致。添加新函数也很容易。
已实现的函数
目前提供以下函数:
| 函数 | 含义 | 语法 |
|---|---|---|
| abs | 绝对值 | number abs(number) |
| acos | 反余弦 | number acos(number) |
| addslashes | 使用反斜杠转义字符串 | string addslashes(string) |
| asin | 弧弦 | number asin(number) |
| atan | 圆弧切线 | number atan(number) |
| atan2 | 圆弧切线变量 | number atan2(number, number) |
| ceil | 向上取整 | number ceil(number) |
| checkdate | 如果是公历中的有效日期,则返回 true(1) | bool checkdate(month,day,year) |
| convert_value | 使用数值的输入表和输出表转换数值 | number convert_value(fValue, iStrict, sTranslateFromList, sTranslateToList) |
| cos | 余弦 | number cos(number) |
| count | 统计列表中已回答(非空)问题的数量 | number count(arg1, arg12, ..., argN) |
| countif | 统计列表中等于第一个参数的已回答问题的数量 | number countif(matches, arg1, arg2, ... argN) |
| countifop | 统计列表中符合条件(arg op value)的已回答问题的数量 | number countifop(op, value, arg1, arg2, ... argN) |
| date | 格式化一个本地时间/日期 | string date(format [, timestamp=time()]) |
| exp | 计算e指数 | number exp(number) |
| fixnum | 必要时使用逗号作为小数点分隔符显示数字 | string fixnum(number) |
| floor | 舍去法取整 | number floor(number) |
| gmdate | 格式化GMT日期/时间 | string gmdate(format [, timestamp=time()]) |
| html_entity_decode | 将所有HTML实体转换为相应的字符(始终使用 ENT_QUOTES 和 UTF-8) | string html_entity_decode(string) |
| htmlentities | 将所有适用的字符转换为HTML实体(始终使用 ENT_QUOTES 和 UTF-8) | string htmlentities(string) |
| expr_mgr_htmlspecialchars | 将特殊字符转换为HTML实体(始终使用 ENT_QUOTES 和 UTF-8) | string htmlspecialchars(string) |
| expr_mgr_htmlspecialchars_decode | 将特殊HTML实体转换回字符(始终使用 ENT_QUOTES 和 UTF-8) | string htmlspecialchars_decode(string) |
| idate | 格式的本地时间/日期作为整数 | string idate(string [, timestamp=time()]) |
| if | Excel风格的 if(test,result_if_true,result_if_false) | if(test,result_if_true,result_if_false) |
| implode | 连接阵列元素和字符串 | string implode(glue,arg1,arg2,...,argN) |
| intval | 得到整数变量的值 | int intval(number [, base=10]) |
| is_empty | 判断变量是否被视为空 | bool is_empty(var) |
| is_float | 判断变量的类型是否为浮点数 | bool is_float(var) |
| is_int | 判断变量的类型是否为整数 | bool is_int(var) |
| is_nan | 发现一个值是否不是一个数 | bool is_nan(var) |
| is_null | 判断变量是否为NULL | bool is_null(var) |
| is_numeric | 判断变量是否为数字或数字字符串 | bool is_numeric(var) |
| is_string | 判断变量的类型是否为字符串 | bool is_string(var) |
| join (New in 2.0 build 130129) | 将元素连接为新的字符串 | join(arg1, arg2, ... argN) |
| list | 返回以逗号分隔的非空值列表 | string list(arg1, arg2, ... argN) |
| listifop (New in 3.16.1 ) | 从列表中符合条件(cmpProp op value)的问题中,返回以“glue”分隔的指定问题属性(retProp)列表 | string listifop(cmpProp, op, value, retProp, glue, sgqa1, sgqa2, ... sgqaN) |
| ltrim | 去除字符串开头的空白字符(或其他字符) | string ltrim(string [, charlist]) |
| max | 找出最高价值 | number max(arg1, arg2, ... argN) |
| min | 找出最小价值 | number min(arg1, arg2, ... argN) |
| mktime | 获取日期的UNIX时间戳(6个参数均为可选) | number mktime([hour [, minute [, second [, month [, day [, year ]]]]]]) |
| modulo-function | 尚不支持取模函数。您可以改用 floor() 函数 | floor(x/y)==(x/y) |
| nl2br | 在字符串中所有换行符之前插入HTML换行标签 | string nl2br(string) |
| number_format | 数字格式按千位分组 | string number_format(number) |
| pi | 找出pi的价值 | number pi() |
| pow | 指数函数表达 | number pow(base, exp) |
| quoted_printable_decode | 将 quoted-printable 字符串转换为8位字符串 | string quoted_printable_decode(string) |
| quoted_printable_encode | 将8位字符串转换为 quoted-printable 字符串 | string quoted_printable_encode(string) |
| quotemeta | 引用元字符 | string quotemeta(string) |
| rand | 产生一个随机整数,请参阅此示例 | int rand() OR int rand(min, max) |
| regexMatch | 将字符串与正规表达式进行比较 | bool regexMatch(pattern,input) |
| round | 数字取整到可选精度 | number round(val [, precision]) |
| rtrim | 去除字符串末尾的空白字符(或其他字符) | string rtrim(string [, charlist]) |
| sin | 正弦 | number sin(arg) |
| sprintf | 返回格式化字符串 | string sprintf(format, arg1, arg2, ... argN) |
| sqrt | 平方根 | number sqrt(arg) |
| stddev | 计算数字列表的样本标准差 | number stddev(arg1, arg2, ... argN) |
| str_pad | 使用另一个字符串将字符串填充到指定长度 | string str_pad(input, pad_length [, pad_string]) |
| str_repeat | 重复字符串 | string str_repeat(input, multiplier) |
| str_replace | 将所有出现的搜索字符串替换为替换字符串 | string str_replace(search, replace, subject) |
| strcasecmp | 二进制安全的字符串比较,不区分大小写 | int strcasecmp(str1, str2) |
| strcmp | 二进制安全的字符串比较 | int strcmp(str1, str2) |
| strip_tags | 剥离字符串的HTML和PHP标签 | string strip_tags(str, allowable_tags) |
| stripos | 查找不区分大小写的Unicode字符串首次出现的位置(从0开始,未找到则返回 false) | int stripos(haystack, needle [, offset=0]) |
| stripslashes | 引号字符串去引号 | string stripslashes(string) |
| stristr | 字符串不区分大小写 | string stristr(haystack, needle [, before_needle=false]) |
| strlen | 获取字符串长度 | int strlen(string) |
| strpos | 查找Unicode字符串首次出现的位置(从0开始,未找到则返回 false) | int strpos(haystack, needle [ offset=0]) |
| strrev | 逆转字符串 | string strrev(string) |
| strstr | 查找字符串首次出现 | string strstr(haystack, needle[, before_needle=false]) |
| strtolower | 将字符串转换为小写 | string strtolower(string) |
| strtotime | 将几乎任何英文日期时间文本描述解析为Unix时间戳 | int strtotime(string) |
| strtoupper | 将字符串转换为大写 | string strtoupper(string) |
| substr | 返回Unicode字符串的一部分 | string substr(string, start [, length]) |
| sum | 计算数组值总和 | number sum(arg1, arg2, ... argN) |
| sumifop | 对列表中符合条件(arg op value)的已回答问题的值求和 | number sumifop(op, value, arg1, arg2, ... argN) |
| tan | 正切 | number tan(arg) |
| time | 返回当前的UNIX时间戳 | number time() |
| trim | 去除字符串开头和结尾的空白字符(或其他字符) | string trim(string [, charlist]) |
| ucwords | 将字符串中每个单词的首字母转换为大写 | string ucwords(string) |
| unique | 如果所有非空回答都是唯一的则返回值为真 | boolean unique(arg1, ..., argN) |
<span id="Create_new_expression_functions_with_plugin_ (New in 4.0.0 )">
使用插件创建新的表达式函数 (New in 4.0.0 )
如果您需要核心中不存在的新函数,可以使用插件创建。此类新函数通过 expressionManagerStart 事件创建。
ExpressionScript 知道哪些变量是本地变量
为了正确构建页面的 JavaScript,EM 需要知道哪些变量是在该页面上设置的,以及它们的 JavaScript ID 是什么(例如用于 document.getElementById(x))。它还必须知道哪些变量是在其他页面上设置的(以确保所需的 <input type='hidden' value='x'> 字段存在并已填充)。
级联条件
如果任何一个变量不相关,则整个方程式都将不相关(false)。例如,在下表中,N/A 表示其中一个变量不相关:
| 运算符 | 示例 | a | b | 结果 |
|---|---|---|---|---|
| + (unary) | +a | N/A | false | |
| ! | !a | N/A | false | |
| == (or eq) | a == b | N/A | 5 | false |
| == (or eq) | a == b | N/A | 0 | false |
| == (or eq) | a == b | N/A | N/A | false |
| != (or ne) | a != b | N/A | 5 | false |
| != (or ne) | a != b | N/A | N/A | false |
| != (or ne) | a != b | N/A | 0 | false |
| > (or gt) | a > b | N/A | 5 | false |
| >= (or ge) | a >= b | N/A | 5 | false |
| < (or lt) | a < b | N/A | 5 | false |
| <= (or le) | a <= b | N/A | 5 | false |
| and | a and b | N/A | 5 | false |
| and | a and b | N/A | N/A | false |
| or | a or b | N/A | N/A | false |
| or | a or b | N/A | 5 | false |
| + | a + b | N/A | 5 | false |
| * | a * b | N/A | 5 | false |
| / | a / b | 5 | N/A | false |
| () | (a) | N/A | false | |
| (exp) | (a && b) | N/A | 5 | false |
| (exp) op (exp) | (b + b) > (a && b) | N/A | 5 | false |
| function | sum(a,b,b) | N/A | 5 | false |
| function | max(a,b) | N/A | 5 | false |
| function | min(a,b) | N/A | 5 | false |
| function | implode(', ',a,b,a,b) | N/A | 5 | false |
| function | if(a,a,b) | N/A | 5 | false |
| function | is_empty(a) | N/A | false | |
| function | is_empty(a) | 0 (or blank) | true | |
| function | !is_empty(a) | N/A | false |
覆盖级联条件
假设您想显示所有相关答案的累计总和。您可能会尝试使用方程式 {sum(q1,q2,q3,...,qN)}。但是,它在内部会被转换为 LEMif(LEManyNA("q1","q2","q3",...,"qN"),"",sum(LEMval("q1"),LEMval("q2"),LEMval("q3"),...,LEMval("qN")))。因此,如果 q1-qN 中的任何值不相关,该方程式将始终返回 false。在这种情况下,在所有问题都回答完之前,sum() 将显示“0”。
为了解决这个问题,可以为每个变量添加“.NAOK”后缀(表示 Not Applicable is OK,即“不适用也可以”)。此时会出现以下行为。假设您有一个变量 q1.NAOK:
- q1 不会被添加到 LEManyNA() 子句中
- LEMval('q1') 将继续检查该反馈是否相关,如果不相关则返回 ""(因此单个不相关的反馈将被忽略,但不会使整个表达式无效)。
因此,解决累计总和问题的办法是使用方程式 sum(q1.NAOK,q2.NAOK,q3.NAOK,...,qN.NAOK)。
使用 .NAOK 后缀还可以让作者设计出具有多条可能路径、但之后又汇合到共同路径的调查。例如,假设受试者的回答超出了正常的反馈范围。作者可以提醒受试者他们可能无法获得有效的结果,并询问他们是否真的要继续该调查。如果他们选择“是”,则会显示其余的问题。“其余问题”的条件将检查最初的反馈是否在正常范围内,或者受试者是否对仅在回答超出正常范围时才相关的那个问题回答了“是”。
ExpressionScript 如何支持条件微定制?
下面是一个微定制示例(其中问题类型=='expr' 表示方程式):
| 问题代码 | 相关性 | 问题类型 | 问题 |
|---|---|---|---|
| name | 1 | text | What is your name? |
| age | 1 | text | How old are you? |
| badage | !is_empty(age) | expr | {(age<16) or (age>80)} |
| agestop | badage | message | Sorry, {name}, you are too {if( (age<16),'young',if( (age>80),'old','middle-aged') ) } for this test. |
| kids | !badage | yesno | Do you have children? |
| parents | 1 | expr | {!badage && kids=='Y'} |
| numKids | parents | text | How many children do you have? |
| kid1 | parents && numKids >= 1 | text | How old is your first child? |
| kid2 | parents && numKids >= 2 | text | How old is your second child? |
| kid3 | parents && numKids >= 3 | text | How old is your third child? |
| kid4 | parents && numKids >= 4 | text | How old is your fourth child? |
| kid5 | parents && numKids >= 5 | text | How old is your fifth child? |
| sumage | 1 | expr | {sum(kid1.NAOK,kid2.NAOK,kid3.NAOK,kid4.NAOK,kid5.NAOK)} |
| report | parents | text | {name}, you said you are {age} and that you have {numKids}. The sum of ages of your first {min(numKids,5)} kids is {sumage} |
要下载上述调查示例,请点击以下链接:子女数量调查示例。
所有这些问题都可以放在同一页面上(例如,在同一个分组中),并且只会显示相关的问题。此外,当您输入子女的年龄时,最后一个问题中的 sum() 表达式将动态更新。
ExpressionScript 通过用一个具名的 <span> 元素包围每个表达式来提供此功能。每次值发生变化时,它都会重新计算应显示在该 <span> 元素中的表达式并重新生成显示内容。同一页面上可以有数十个甚至数百个这样的定制表达式。
语法高亮
为了帮助输入和验证表达式,EM 提供了具有以下功能的语法高亮:
语法高亮的类型和含义
| 颜色 | 示例 | 含义 | 工具提示 | 备注 |
|---|---|---|---|---|
| 黄褐色背景 | Sample | 整个方程式 | 无 | 花括号内任何被识别为方程式的内容(例如,前后没有空白字符)都将以黄褐色背景标记,以便与周围的文本区分开来 |
| 红色粗体文字 | Sample | 错误 | 对错误的一些说明 | 可能是未知变量或函数错误。调查已损坏,依赖相应表达式的问题将不会显示给受访者。 |
| 蓝色文字 | Sample | 函数名 | 含义及允许的语法 | 指函数名,或后面带有左括号、应为函数的内容。它们以蓝色粗体文字显示。工具提示会显示该函数的含义及允许的语法。 |
| 灰色文字 | Sample | 字符串 | 无 | 单引号和双引号字符串以灰色文字显示。 |
| 青色文字 | Sample | 在同一页面上设置的变量, | [name]:问题;值;列出每个值对应代码的答案列表 | 在您当前正在编辑的问题所在的同一页面上设置的任何变量都以青色文字显示(可以通过 JavaScript 更新)。工具提示会显示其名称、实际问题及其当前值(如未设置则为空)。如果问题类型期望的反馈来自一组枚举值,则会显示代码与显示值之间的对应关系。 |
| 绿色文字 | Sample | 在前面的页面上设置的变量 | [name]:问题;值;列出每个值对应代码的答案列表 | 在前面的页面上设置的任何变量都以绿色粗体文字显示。工具提示会显示其名称、实际问题及其当前值(如未设置则为空)。如果问题类型期望的反馈来自一组枚举值,则会显示代码与显示值之间的对应关系。 |
| 粉色粗体文字 | Sample | 在后面的页面上设置的变量 通常:调查开始时为空,但可以通过索引或返回上一页来填充 | [name 或 SGQA 代码]:问题;值;列出每个值对应代码的答案列表 | 在后面的页面上设置的任何变量都以粉色粗体文字显示。工具提示会显示其名称、实际问题及其当前值(如未设置则为空)。如果问题类型期望的反馈来自一组枚举值,则会显示代码与显示值之间的对应关系。 |
| 黄褐色粗体文字 | Sample | LimeSurvey 替换值 | 该值 | LimeSurvey 替换字符串(如 {TOKEN:xxx}、{PRIVACY_MESSAGE})以黄褐色粗体文字显示。 |
| 红色文字 | Sample | 赋值运算符 (=) | 警告信息 | 如果您使用了赋值运算符 (=),该运算符将以红色文字显示。这是为了避免在您本想检查 a == b 时,不小心设置了 a = b 而造成值被意外重新赋值。 |
| 普通黑色文字 | Sample | 标点符号 | 无 | 表达式中的所有其他标点符号均以普通黑色文字显示。 |
| 红框文字 | 粗红线围绕着错误 | 语法错误 | 错误描述 | 检测到的任何语法错误都显示在红框内。工具提示会显示该错误。示例包括括号不匹配、使用未定义的函数、向函数传递的参数数量错误、表达式结构不良(例如变量之间缺少运算符)、尝试为只读变量赋新值、尝试为非变量赋值,或使用不支持的语法。请注意,即使表达式中存在多个错误,语法错误检测系统可能只会报告一个错误;但只要检测到错误,至少会显示一个。 |
延伸阅读
ExpressionScript 示例调查
使用案例和操作指南
分步示例
开发人员参考