Expression Manager
From LimeSurvey Manual
Краткое руководство
Обзор
При настройке опросов обычно требуется возможность задать следующее:
- Навигация/ветвление — позволяет ответам респондента менять порядок, в котором задаются вопросы.
- Адаптация/подстановка (piping) — как сформулировать вопрос (например, сослаться на предыдущие ответы или изменить формулировку в зависимости от количества или пола респондентов) или как создавать пользовательские отчёты (например, оценки или персональные рекомендации).
- Проверка — контроль того, что ответы соответствуют определённым критериям, например минимальным и максимальным значениям или заданному шаблону ввода.
Expression Manager (EM) предоставляет удобный способ задать логику для каждой из этих функций. Практически всё, что можно записать как стандартное математическое уравнение, является допустимым выражением, даже если в нём вызываются функции. Сейчас EM предоставляет доступ к 80 функциям, и их набор можно легко расширить. Он также позволяет обращаться к переменным по понятным именам (а не по именам SGQA).
В следующих разделах показаны основные места, где используется Expression Manager.
Условие (управление навигацией/ветвлением)
В некоторых опросах используется «логика перехода» (Goto Logic): если вы ответили на вопрос 1 вариантом C, то переходите к вопросу 5. Такой подход сильно ограничивает возможности: его трудно проверить, и он легко ломается при изменении порядка вопросов. EM использует булево уравнение условия, которое задаёт все обстоятельства, при которых вопрос может быть допустимым. Если вопрос соответствует условию, он отображается, в противном случае он считается неприменимым, и в базе данных сохраняется значение NULL. Это похоже на то, что можно сделать с помощью редактора условий, но EM позволяет легко задавать гораздо более сложные и мощные критерии (и использовать имя переменной вместо имён SGQA).
На этом изображении показан один из способов просмотра логики условий опроса. Опрос вычисляет индекс массы тела. Уравнение условия показано в квадратных скобках сразу после имени переменной (зелёного цвета). Так, условие для weight, weight_units, height и height_units равно 1, то есть эти вопросы задаются всегда. Однако условие для BMI — {!is_empty(height) and !is_empty(weight)}, что означает, что BMI будет вычисляться, только если респондент ввёл значения и роста, и веса (это исключает риск ошибки деления на ноль). Кроме того, вопрос Report отображается, только если респондент ответил на все четыре основных вопроса (height, height_units, weight, weight_units).
Условие отображается и редактируется в следующих местах:
Просмотр и редактирование условия на уровне вопроса
Это уравнение вычисляет индекс массы тела (BMI). Вопрос задаётся, только если человек сначала вводит свой рост и вес.
Это экран редактирования вопроса BMI.
Обратите внимание: при вводе поля «Уравнение условия» фигурные скобки не используются.
Просмотр и редактирование условия на уровне группы
Это пример опроса-переписи. На первой странице спрашивается, сколько человек проживает с вами, и это число сохраняется в переменной «cohabs». Эта страница отображается, только если вместе с вами проживает более одного человека (то есть она показывается для второго проживающего с вами человека), а также только если вы указали, кем вам приходится первый человек (p1_rel).
Как видите, у группы также есть критерии условия на уровне вопросов: каждый вопрос появляется, только когда вы ответили на предыдущий (например, {!is_empty(p1_sex)}). EM автоматически объединяет условия группы и вопросов. Вопросы в группе задаются, только если группа в целом соответствует условию. Затем задаются только те вопросы группы, которые соответствуют своим условиям.
Вот экран редактирования условия на уровне группы для этого вопроса:
Обратите внимание: при вводе поля «Уравнение условия» фигурные скобки не используются.
Адаптация/подстановка (Tailoring/Piping)
EM позволяет легко выполнять простую и сложную условную адаптацию ваших вопросов. Иногда нужна простая подстановка, например: «Вы сказали, что приобрели [Продукт]. Что вам понравилось в нём больше всего?». Иногда нужна условная подстановка, например: «[Г-н/Г-жа] [Фамилия], согласны ли вы пройти наш опрос?». В этом случае нужно выбирать обращение в зависимости от пола человека. В других случаях требуется ещё более сложная подстановка (например, на основе математических вычислений). EM поддерживает все эти виды адаптации/подстановки.
Условные уравнения
Пример с индексом массы тела показывает, как можно вычислить BMI человека, позволяя при этом вводить рост и вес в метрических или неметрических единицах.
Здесь weight_kg равно {if(weight_units == 'kg', weight, weight * .453592)}. Функция if() означает: если респондент указал вес в килограммах, используется это значение, в противном случае введенное значение (в фунтах) умножается на .453592 для перевода в килограммы. Переменная height_m использует аналогичный подход для вычисления роста в метрах, даже если рост был введен в дюймах.
BMI вычисляет формулу веса как {weight_kg / (height_m * height_m)}.
Наконец, отчет условно адаптирует сообщение для респондента, сообщая ему, что он ввел. («Вы сказали, что ваш рост 2 метра, а вес 70 кг.»)
Хотя это плохо видно на изображении выше, weight_status использует вложенные операторы if() для отнесения человека к категории от недостаточного веса до тяжелого ожирения. Его уравнение можно увидеть в представлении Show Logic View.
В окне редактирования этого вопроса можно увидеть две вещи:
- При адаптации выражения должны быть заключены в фигурные скобки
- Выражения могут занимать несколько строк, если, как в данном случае, нужно упростить чтение вложенной условной логики.
Адаптированные вопросы, ответы и отчеты
В этом примере показан отчет BMI.
Вот окно редактирования того же вопроса.
Как видите, все, что находится в фигурных скобках, рассматривается как выражение, поэтому на предыдущем изображении оно подсвечено (выделено цветом). Если вы допустите опечатки (например, неверно написанные или неопределенные имена переменных или функций), EM покажет ошибку, как здесь: height_unit — неопределенное имя переменной (на самом деле это height_units), а rnd() — неопределенная функция (правильное имя функции — round()). В обоих случаях ошибки обведены красной рамкой, чтобы их было проще заметить и исправить.
Также видно, что можно быстро создавать сложные отчеты, например таблицу введенных значений или персональные рекомендации.
Помните, что при адаптации все выражения должны быть заключены в фигурные скобки, чтобы LimeSurvey знал, какие части вопроса являются обычным текстом, а какие нужно обрабатывать через Expression Manager.
Проверка
EM управляет работой большинства расширенных параметров вопросов. Они определяют такие аспекты, как минимальное/максимальное количество ответов, минимальные/максимальные отдельные значения, минимальную/максимальную сумму значений, а также проверку соответствия введенных значений заданным шаблонам строк. Вы по-прежнему вводите эти расширенные параметры вопросов как обычно. Однако теперь любое значение в этих полях считается выражением, поэтому можно задавать критерии минимума/максимума со сложными условными связями с другими вопросами.
Во всех этих случаях, поскольку расширенный параметр вопроса всегда считается выражением, фигурные скобки при его указании не используются.
На страницах примеров опросов показано множество рабочих примеров использования выражений для проверки.
Введение
LimeSurvey использует новый модуль Expression Manager (EM), который позволит LimeSurvey поддерживать более сложное ветвление, оценки, проверку и адаптацию. Он заменит способ, которым LimeSurvey управляет заменами, условиями и оценками на серверной стороне. Он также значительно ускорит обработку, так как исключает большинство обращений к базе данных во время выполнения. EM разработан доктором Томасом Уайтом (TMSWhite).
Эта вики-страница является основным справочником по синтаксису и возможностям Expression Manager.
Основные определения
- Выражение: всё, что заключено в фигурные скобки
- При условии, что сразу после открывающей скобки или перед закрывающей фигурной скобкой нет пробела
- Содержимое выражений вычисляется EM, поэтому оно может содержать математические формулы, функции, а также сложную обработку строк и дат.
- Адаптация текста: иногда её называют «пайпингом», это процесс условного изменения текста
- Вам доступны все «поля замены», данные участника и данные ответов.
- Кроме того, вам проще получать доступ к вопросам, ответам и их свойствам.
- Уравнение условия: новый атрибут вопроса, управляющий видимостью вопроса
- Если задано уравнение условия, вопрос отображается, только если условие принимает значение «истина».
- Внутри системы все команды array_filter и array_filter_exclude преобразуются в условия уровня подвопроса
- Уравнение (тип вопроса): новый тип вопроса, который сохраняет вычисления или отчёты в базе данных
- Он похож на вопрос типа «Шаблон», но его содержимое сохраняется в базе данных, даже если задана настройка «Скрыть этот вопрос»
- Код вопроса: предпочтительное имя переменной для EM
- Это может быть описательное имя, указывающее на назначение вопроса, что упрощает чтение сложной логики
- Допустимые коды вопросов НЕ должны начинаться с цифры, поэтому для нумерации вопросов с помощью кода вопроса просто используйте «q1», «q1a» или «g1q2».
- Именно этот код сейчас становится именем переменной при экспорте данных в SPSS или R, поэтому, если вы выполняете статистический анализ, вы, вероятно, уже сделали его уникальным.
Обязательно ли использовать EM?
Краткий ответ: нет (но в некотором смысле да).
EM полностью обратно совместим с существующими опросами. Поэтому, если вас устраивает использование условий и оценок в том виде, в каком они были в LimeSurvey версий <= 1.91+, вы можете продолжать работать так же.
Однако EM полностью заменяет способ, которым LimeSurvey внутренне обрабатывает условия. Хотя вы по-прежнему можете использовать редактор условий для создания и управления условиями, LimeSurvey 1.92 преобразует их в эквивалентные уравнения условия. В рамках обновления LimeSurvey 1.92 автоматически преобразует все существующие условия в уравнения условия.
Это должно дать вам лучшее из двух миров: вы можете продолжать пользоваться LimeSurvey привычным образом, но при этом видеть эквивалентное уравнение условия и постепенно переходить на прямое использование уравнений условия, когда сочтете нужным.
Можно ли одновременно использовать условия и Relevance?
Да. Для одних вопросов можно использовать редактор условий, а для других — редактор Relevance. При сохранении вопроса условия автоматически преобразуются в Relevance.
Обратите внимание: если вы используете редактор условий, предполагается, что эти условия должны перезаписать любое уравнение Relevance, введённое вручную. Поэтому если у вас уже есть условия и вы хотите вручную изменить Relevance, сначала удалите условия для этого вопроса. Для этого скопируйте сгенерированное уравнение условия в текстовый редактор, с помощью меню условий удалите все условия для этого вопроса (при этом будет удалено и условие Relevance), затем отредактируйте вопрос и вставьте сгенерированное уравнение условия из текстового редактора обратно в поле Relevance этого вопроса (и сохраните вопрос). Если будет достаточно запросов на удаление условий без удаления сгенерированного уравнения условия, мы сможем добавить процесс массового преобразования.
Что выбрать: условия или Relevance?
Ниже приведён список плюсов и минусов каждого подхода:
| Подход | Плюсы | Минусы |
|---|---|---|
| Условия | 1. Удобный графический интерфейс для создания простых условий 2. Интерфейс хорошо документирован и знаком службе поддержки |
1. Поддерживает только простые сравнения и плохо работает с условиями AND/OR 2. Каскадные условия работают нестабильно 3. Медленная работа — большая нагрузка на базу данных, поэтому длинные опросы могут тормозить 4. Есть сообщения о проблемах при повторной загрузке условий 5. Интерфейс плохо масштабируется, если вопросов десятки, сотни или тысячи 6. Перенос бумажных опросов может быть медленным, так как нужно использовать имена SGQA 7. Для сложного ветвления часто требуется программист, чтобы написать нужную логику |
| Relevance | 1. Поддерживает очень сложную логику, включая более 80 функций и математические/строковые операторы 2. Отличная поддержка каскадной логики 3. Высокая скорость — нет дополнительных обращений к базе данных, поэтому поддерживаются опросы с более чем 1000 вопросов 4. Нет проблем с повторной загрузкой логики, так как не требуются коды SGQA 5. Подсветка синтаксиса масштабируется на опросы с более чем 1000 вопросов 6. Просто и быстро использовать командам, которые хотят перевести существующие бумажные опросы в электронный вид. 7. Легко поддерживает полуструктурированные интервью и эпидемиологические опросы без участия программистов |
1. Нет графического интерфейса для простых условий — вместо него используется подсветка синтаксиса 2. Это новинка, поэтому службы поддержки ещё не освоили EM. |
Итог таков: если вас устраивает, как работает LimeSurvey 1.91+, нет причин менять привычный подход.
Какие ещё преимущества даёт использование EM?
Вот ещё несколько причин, по которым стоит использовать EM.
- Вычисления — вы можете создать любое вычисление, какое только придумаете:
- Вам доступны все распространённые математические операторы и функции
- Вам доступно более 70 функций для обработки математических данных, дат и строк
- Разработчикам довольно просто добавлять новые функции, если они нужны пользователям
- Сохранение вычислений в базе данных
- Теперь можно выполнять простые и сложные вычисления и/или подсчёт баллов по шкале И сохранять их в базе данных без использования JavaScript.
- Для этого используется тип вопроса «Уравнение».
- Оценки ответов
- Теперь можно создавать оценки ответов или баллы по шкале для любого типа вопроса, а не только для ограниченного набора, как раньше
- С помощью адаптации текста можно показывать промежуточные или итоговые баллы в любом нужном месте — даже на той же странице
- У вас больше контроля над отчётами, формируемыми на основе этих баллов
- Баллы можно сохранять в базе данных без использования JavaScript
- Баллы можно скрывать без использования JavaScript или CSS
- Поля подстановки
- Используйте код вопроса, чтобы вставлять значения ответов на предыдущие вопросы — так проще читать и проверять логику.
- Адаптация текста (Tailoring) — можно условно отображать текст в зависимости от других значений
- Используйте подходящее обращение к участнику, например: «Здравствуйте, [г-н/г-жа] Смит»
- Формируйте грамматически правильные предложения там, где важно единственное/множественное число: например, «У вас 1 ребёнок» и «У вас 2 ребёнка»
- Правильно спрягайте глаголы и склоняйте существительные в зависимости от пола и числа участников.
- Новые атрибуты переменных — для адаптации текста вам доступны следующие:
- (без суффикса) — псевдоним для qcode.code
- .code — выбранный код ответа на вопрос, если вопрос актуален (иначе пусто), либо текстовое значение, если вопрос не имеет кодов ответов
- .NAOK — то же, что и .code, но может использоваться в вычислениях и списках, даже если вопрос неактуален
- .value — значение оценки ответов для вопроса, если вопрос актуален (иначе пусто), либо текстовое значение, если вопрос не имеет кодов ответов — доступно, если для опроса включены оценки ответов, иначе всегда равно нулю
- .valueNAOK — то же, что и .value, но может использоваться в вычислениях и списках, даже если вопрос неактуален
- .shown — ответ в том виде, в каком он показан пользователю
- .qid — ID вопроса
- .gid — ID группы
- .sgqa — значение SGQA для вопроса
- .jsName — корректное имя переменной JavaScript для вопроса независимо от того, определена ли она на этой или на другой странице
- .qseq — порядковый номер вопроса (начиная с 0)
- .gseq — порядковый номер группы (начиная с 0)
- .mandatory — является ли вопрос обязательным (Y/N)
- .question — текст вопроса
- .relevance — уравнение условия для вопроса
- .grelevance — уравнение условия для группы
- .relevanceStatus — актуален ли вопрос в данный момент (логическое значение; в PHP и JS может отличаться)
- .type — тип вопроса (односимвольный код)
- Динамические изменения на странице
- Все условия, вычисления и адаптация текста работают динамически на странице — при изменении значений страница обновляется мгновенно
- Вопросы динамически появляются и исчезают в зависимости от того, актуальны ли они
- Вопросы также динамически адаптируются на основе ответов на странице, поэтому вы можете видеть промежуточные итоги, адаптированные предложения и персонализированные отчёты.
- Новый экран ввода данных
- Помимо текущей системы ввода данных, можно использовать режим «Всё в одном».
- Он поддерживает условия и адаптацию текста на странице, поэтому операторы ввода данных могут быстро переходить между полями и вводить только актуальные ответы
- Это может быть критично, если оператору ввода данных нужно видеть адаптацию текста, которая тоже динамическая.
- Устраняет необходимость в большинстве пользовательских скриптов JavaScript
- EM легко поддерживает сложные вычисления, подсчёт баллов, адаптацию текста и условную логику.
- Для некоторых задач JavaScript по-прежнему понадобится (например, для пользовательских макетов и условного скрытия подэлементов вопроса), но ваш JavaScript может использовать функции EM, чтобы обращаться к вопросам по их Qcode вместо SGQA и получать любые свойства вопроса, перечисленные выше.
Какие ещё полезные новые возможности даёт EM?
Независимо от того, продолжаете ли вы использовать редактор условий или составляете уравнения условия вручную, вы получаете следующие дополнительные преимущества:
- Вы можете создавать более сложные критерии проверки
- Все расширенные атрибуты вопроса (например, max_answers, min_num_value_n, max_num_value) могут использовать выражения. Поэтому критерии минимума/максимума легко настраиваются на основе предыдущих ответов, даже если они находятся на той же странице.
- EM также обрабатывает всю проверку на основе регулярных выражений, поэтому вы можете надёжно сочетать атрибуты вопроса на основе preg и уравнений.
- Простое изменение порядка (или удаление) вопросов и групп
- До версии 1.92 нельзя было изменить порядок вопросов или групп, если LimeSurvey считал, что это может нарушить условия, в которых они используются. Точно так же нельзя было удалить вопросы, если от них зависели другие вопросы.
- Благодаря подсветке синтаксиса EM легко увидеть и проверить, не используете ли вы вопросы до их объявления. Поэтому теперь вы можете изменять порядок или удалять вопросы и группы в любое время. EM обновит подсветку синтаксиса, чтобы показать возможные ошибки.
- Представление изменения порядка вопросов улучшено для такой проверки. Теперь оно показывает уравнение условия вопроса и персонализацию, поэтому вы сразу увидите, окрасились ли какие-либо переменные в розовый цвет (это означает, что они используются до объявления).
- Навигационный индекс вопросов/групп всегда доступен и точен
- До версии 1.92 эти индексы были недоступны при наличии сложных условий
- С EM мы можем гарантировать их точность.
- Респонденты могут даже вернуться к предыдущему вопросу, изменить ответ, а затем перейти вперёд (или отправить ответы)
- При переходе вперёд EM повторно проверит все промежуточные вопросы/группы.
- Если какие-либо вопросы перестают быть актуальными, в базе данных для них будет записано значение NULL, чтобы ваши данные оставались согласованными.
- Если какие-либо вопросы становятся актуальными или впервые не проходят правила обязательности или проверки, EM остановится на этой странице и потребует от пользователя ответить на эти вопросы, прежде чем перейти к конечному месту назначения.
- Автоматическое преобразование условий в уравнения условия
- При обновлении базы данных для всех существующих опросов с условиями будут сгенерированы уравнения условия
- При каждом импорте опроса уравнения условия будут создаваться по мере необходимости
- При каждом добавлении, удалении или изменении условий EM будет генерировать соответствующее уравнение условия.
- Удобная подсветка синтаксиса
- Когда EM показывает уравнение условия, он показывает Qcode, даже если вы ввели код SGQA, так как мы полагаем, что так его легче читать.
- Все переменные выделены цветом, показывающим, были ли они объявлены до или после текущего вопроса (или до или после текущей группы). Это позволяет быстро обнаружить и исправить случаи, когда вы пытаетесь использовать переменные в условиях (включая array_filter), персонализации или уравнениях проверки до их объявления.
- Кроме того, если навести указатель мыши на выделенную цветом переменную, вы увидите самые важные метаданные об этом вопросе. Это номер группы в последовательности, номер вопроса в последовательности, Qcode, текст вопроса и все доступные варианты ответов (если это тип вопроса с перечисляемыми вариантами ответов).
- Список вариантов ответов использует следующий синтаксис: 'answers':{key:val, ... }.
- key имеет синтаксис 'scale~code', где scale — шкала ответов (например, для двойной шкалы), а code — код ответа.
- val имеет синтаксис 'value~shown', где value — значение оценки ответов (если используются оценки, иначе code) (например, Qcode.value), а shown — отображаемое значение, которое видит респондент (например, Qcode.shown)
- Это означает, что во многих опросах можно использовать вычисления без режима оценки. Если у вас есть перечисляемые варианты ответов, которые уникальны, нецелые... точнее, не десятичные и неотрицательные, вы можете просто выполнять вычисления над значениями Qcode.code.
- Простой обзор всей логики и содержимого опроса
- Новая функция «Показать логику опроса» позволяет увидеть всё об опросе (или группе, или вопросе) на одной странице.
- Она показывает сведения на уровне группы, вопроса, подвопроса и ответа для выбранной области (опрос, группа или вопрос)
- Она также показывает условие, условие на уровне подвопроса (для array_filter и array_filter_exclude), сгенерированное уравнение проверки (для preg и любых правил проверки, таких как минимум/максимум суммы/количества значений) и все непустые атрибуты вопроса.
- Всё выделено подсветкой синтаксиса, чтобы вы могли увидеть возможные синтаксические ошибки (например, несбалансированные скобки или использование переменных до их объявления)
- Подсветка синтаксиса помогает быстро перемещаться по опросу и редактировать его.
- Если щёлкнуть имя переменной, откроется окно (или вкладка) браузера, где показан этот вопрос и где его можно редактировать.
- Если щёлкнуть имя группы, откроется окно (или вкладка) браузера с представлением изменения порядка в группе, чтобы можно было легко переставлять вопросы.
- Все атрибуты вопроса тоже выделены подсветкой синтаксиса. Это позволяет задавать и просматривать выражения в расширенных параметрах вопроса (например, основывать максимум/минимум количества/суммы значений на выражении)
- Автор EM использовал похожее представление (немного чище), чтобы его коллеги-эпидемиологи и этический комитет могли проверять и утверждать опросы с тысячами вопросов в сильно разветвлённых и персонализированных структурированных интервью
Начало работы
Лучший способ начать работу с EM:
- Установите последнюю стабильную версию с http://www.limesurvey.org/en/download
- Импортируйте и изучите примеры опросов.
- Изучите сценарии использования и инструкции и пошаговые примеры.
- Изучите документацию по EM (эту страницу)
- Изучите встроенный набор тестов EM
- В любом опросе в разделе «Инструменты» выберите параметр EM
- В разделе «Доступные функции» перечислены более 70 функций и их синтаксис
- Модульные тесты отдельных выражений
- показывают примеры использования всех функций и операторов EM, а также результаты в PHP и JavaScript
- обратите внимание, что есть несколько функций, которые дают разные результаты в версиях PHP и JavaScript, поэтому эта страница поможет вам спланировать логику EM соответствующим образом.
Какую функциональность расширяет/заменяет Expression Manager? (LimeSurvey <= 1.91+)
Условия => Уравнения условия
Условия определяли, какие вопросы видны. Общий синтаксис был SGQA оператор Значение, например 111X2X3 == "Y". Условия можно было объединять через И или ИЛИ, но смешивать И и ИЛИ было сложно. Сами условия хранились в отдельной таблице, и значительная часть кода LimeSurvey была посвящена управлению условиями. Из-за интенсивного обращения к базе данных обработка большого количества условий могла вызывать заметное снижение производительности. Кроме того, после назначения условий вопросам или группам их часто нельзя было переупорядочивать или удалять.
Оценки ответов => Уравнения и микроперсонализация
Оценки ответов позволяли пользователям создавать шкальные баллы из набора вопросов. Однако они не могли динамически изменяться на текущей странице, а их значения не сохранялись в базе данных.
Подстановки => Микроперсонализация
Пользователи могли персонализировать некоторые сообщения и вопросы на основе предыдущих ответов. Например, вопрос мог выглядеть так: {TOKEN:FIRSTNAME}, вы сказали, что {SPORT.shown} — ваш любимый вид спорта. Однако без сложного JavaScript нельзя было выполнять условную персонализацию (например, писать «г-н» или «г-жа» в зависимости от пола человека), спрягать глаголы или склонять существительные. Авторы могли создавать опросы, которые казались персонализированными, но для этого требовались отдельные вопросы для каждой комбинации и сложные условия для выбора отображаемых вопросов.
Проверка
Вопросы можно было проверять с помощью регулярных выражений, минимальных/максимальных значений или использовать ответ в качестве минимального или максимального значения. Однако без сложного JavaScript проверка не могла основываться на вычислениях с другими переменными.
Уравнения
Уравнения не поддерживались без сложного JavaScript.
Тип вопроса «Уравнение»
Уравнения нельзя было сохранить в базе данных (например, итоговый балл оценки ответов) без сложного JavaScript.
Как Expression Manager заменит/расширит эту функциональность?
Expression Manager — это новый основной модуль LimeSurvey, который значительно упрощает поддержку сложной функциональности, для которой раньше требовался собственный JavaScript. Он также заменяет способ, которым LimeSurvey в настоящее время управляет условиями и оценками ответов.
Новая терминология при описании Expression Manager (EM)
EM «мыслит» свою функциональность в следующих терминах:
- Ветвление на основе условий — если вопрос актуален, он задаётся, в противном случае нет (т. е. становится невидимым, а в базе данных помечается как NULL). Для всех типов вопросов, а также для каждой группы есть новое поле условия (так что вы можете применить набор условий ко всей группе, не копируя одно и то же условие в каждый вопрос, и/или объединять условную логику уровня группы и уровня вопроса).
- Персонализация — после того как определено, какие вопросы нужно задавать, персонализация (иногда называемая piping) определяет, как именно задавать вопрос. Это позволяет поддерживать не только простую подстановку (например, {TOKEN:FIRSTNAME}), но и спряжение глаголов и склонение существительных в зависимости от пола или числа респондентов. Она также позволяет менять сообщение, которое вы показываете респонденту, в зависимости от того, ответил ли он на другие вопросы (или как он на них ответил).
- Уравнения — EM добавляет новый тип вопроса «Уравнение», который сохраняет результат выражения. Результаты этих уравнений вычисляются и записываются в базу данных, даже если вы скрываете их на странице. Поэтому они полезны для скрытых расчётов баллов, навигации на основе сложных уравнений, оценок ответов и отчётов, которые должны генерироваться и быть легко доступны в базе данных.
Условие и каскадное условие
Теперь у каждого типа вопроса есть параметр условия, который определяет, отображается ли вопрос. EM обрабатывает каждое уравнение условия в том порядке, в котором вопросы должны появляться в опросе. Если выражение истинно (или отсутствует — для поддержки устаревших опросов), вопрос будет отображён. Если он не актуален, вопрос будет скрыт, а в базе данных для него будет записано значение NULL. Если в группе нет актуальных вопросов, вся группа будет пропущена.
Более того, если любая из переменных в выражении не актуальна, выражение всегда принимает значение «ложь». Это обеспечивает каскадное условие, так что вам не нужно писать очень длинные уравнения условия для каждого вопроса.
Допустим, у вас есть 5 вопросов Q1–Q5, и вы хотите показывать Q2, только если был дан ответ на Q1, Q3 — если был дан ответ на Q2 и т. д. Уравнения условия могут быть такими:
| Код вопроса | Условие | Вопрос |
|---|---|---|
| Q1 | 1 | Как вас зовут? |
| Q2 | Q1 | {Q1}, сколько вам лет? |
| Q3 | Q2 | Итак, вам {Q2} лет. Вы женаты? |
| Q4 | Q3 == "Y" | {Q1}, как давно вы женаты? |
| Q5 | Q4 | Сколько у вас детей, {Q1}? |
Вычисления условий также работают в JavaScript, поэтому вы можете разместить все вышеуказанные вопросы на одной странице, и всё будет работать как ожидается. На самом деле EM полностью заменяет способ обработки форматов опроса «весь опрос сразу», «по группам» и «по одному вопросу». Теперь все они используют один и тот же механизм навигации, поэтому работают одинаково независимо от формата опроса.
Пока вы остаётесь на той же странице, все введённые вами данные сохраняются, просто скрыты. Поэтому если вы введёте информацию, затем выберете вариант, который делает вопросы неактуальными, а потом снова сделаете их актуальными, ваши ответы по-прежнему будут доступны. Однако как только вы перейдёте на другую страницу, все неактуальные ответы будут потеряны ради целостности набора данных.
Условие на уровне группы
Expression Manager также поддерживает условия на уровне группы. Это упрощает реализацию циклов. Допустим, вы хотите собрать информацию не более чем о 10 объектах (например, о товарах или о людях, проживающих в одном доме). Сначала вы определяете, по скольким объектам нужны уточняющие вопросы (например, спрашиваете, сколько человек живёт в доме, или просите респондентов отметить понравившиеся товары в длинном списке). Когда вы знаете, сколько объектов требуют уточнения, для каждой из 10 групп уточняющих вопросов можно использовать условие на уровне группы, например {count >= 1}, {count >=2}, ... {count >= 10}. Внутри каждой группы можно задать условную логику на уровне вопросов (например, уточняющие вопросы для каждого человека в зависимости от пола или возраста). Для определения того, что именно показывать, условия на уровне вопроса и на уровне группы объединяются по логическому И.
Адаптация текста / подстановка
Всё, что заключено в фигурные скобки, теперь рассматривается как выражение (за одним исключением, описанным ниже). В выражениях доступны все LimeReplacementFields, все переменные (через несколько псевдонимов), все типичные операторы (математические, логические и операторы сравнения) и десятки функций (которые работают динамически даже на стороне клиента).
С помощью этих уравнений вы можете, например:
- Условно показывать респондентам адаптированные сообщения на основе предыдущих ответов
- Создавать оценки ответов и показывать их результаты (или условно переходить к другим вопросам либо показывать сообщения на основе этих результатов), не используя сам модуль оценки ответов
- Спрягать глаголы и склонять существительные в вопросах, ответах и отчётах
- Показывать сводку ответов перед страницей «Показать ваши ответы» в конце опроса
Уравнения
Появился новый тип вопроса — «Уравнение». Он похож на вопрос «Шаблонный текст», но значение отображаемого содержимого сохраняется в базе данных. Поэтому, если текст вопроса типа «Уравнение» содержит вычисление оценки, это значение будет сохранено в базе данных в переменной, которую можно отображать в общей или закрытой статистике.
Это решает частую задачу — сохранение баллов оценки ответов в базе данных.
Синтаксис
Всё, что заключено в фигурные скобки, теперь считается выражением (с одним исключением: в начале и в конце не должно быть пробельных символов — это необходимо, чтобы Expression Manager не пытался обрабатывать встроенный 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',
''
)
)
)
)
)
)
)
)}
- Все стандартные математические операторы (например, +,-,*,/,!)
- Все стандартные операторы сравнения (например, <,<=,==,!=,>,>=, а также их эквиваленты: lt,le,eq,ne,gt,ge)
- Круглые скобки (для группировки подвыражений)
- Условные операторы (например, &&,| | и их эквиваленты: and,or)
- Строки в одинарных и двойных кавычках (в каждой из них можно вкладывать строки с кавычками другого типа)
- Оператор «запятая» (позволяет задать список выражений и вернуть только последний результат)
- Оператор присваивания (=)
- Предопределённые переменные (для обращения к вопросам, атрибутам вопросов и ответам), например все коды SGQA
- Предопределённые функции (их уже более 70, и можно легко добавить новые)
Операторы
Синтаксис EM следует обычному приоритету операторов:
| Уровень | Оператор(ы) | Описание |
|---|---|---|
| 1 | () | круглые скобки для группировки или вызова функций |
| 2 | ! - + | унарные операторы: НЕ, отрицание, унарный плюс |
| 3 | * / | умножение, деление |
| 4 | + - | сложение, вычитание |
| 5 | < <= > >= lt le gt ge | сравнение (больше/меньше) |
| 6 | == != eq ne | сравнение на равенство |
| 7 | and | логическое И |
| 8 | or | логическое ИЛИ |
| 9 | = | оператор присваивания |
| 10 | , | оператор «запятая» |
Предупреждение об операторе «плюс» (+)
Для согласованности между JavaScript и PHP оператор «плюс» (+) выполняет сложение, если оба операнда числовые, и конкатенацию, если обе части — нечисловые строки. Однако мы рекомендуем использовать функцию join() для конкатенации и функцию sum() для сложения, так как это яснее выражает ваше намерение и позволяет избежать неожиданных результатов, если вы ожидали строки, а получили числа (или наоборот).
Предупреждение о несоответствии числа и строки и об алфавитном сравнении
При сравнении значений (больше/меньше или равенство) обращайте внимание на несоответствие типов. Значение, введённое пользователем, или выбранный код ответа можно использовать как число, если это явно число.
Если заключить одно из значений в ", сравнение будет выполняться как текстовое (алфавитное). Если вы хотите сравнивать числа, никогда не заключайте число в кавычки ".
Например, Q0.NAOK > "50" истинно, если Q0.NAOK — числовой вопрос со значением 9. Это происходит потому, что оператор > выполняет алфавитное, а не числовое сравнение.
Чтобы гарантированно сравнивать целые числа, можно использовать intval(Q0.NAOK) > 50, но помните: если Q0.NAOK не является числом (пустое значение или строка), то intval(Q0.NAOK) == 0. Для сравнения строк ("A" < "B") используйте напрямую strcmp: strcmp(Q0.NAOK,"B") или strcmp(Q0.NAOK,"A5").
Предостережение об использовании оператора присваивания (=)
Старайтесь не использовать операторы присваивания без крайней необходимости, так как они могут вызвать неожиданные побочные эффекты. Например, если изменить значение предыдущего ответа, каскадная логика условий и проверки между этим вопросом и текущим не будет пересчитана, и в итоге данные могут оказаться внутренне противоречивыми (например, вопросы останутся с ответами, хотя должны были быть сброшены в NULL, или будут пропущены вопросы, на которые нужно было ответить). Как правило, если вы хотите присвоить значение переменной, следует создать вопрос типа «Уравнение» и задать его значение с помощью выражения. Тем не менее иногда этот оператор действительно необходим, поэтому мы сделали его доступным.
Чтобы предостеречь вас при использовании этого оператора, в формулах синтаксиса он отображается красным шрифтом (чтобы вы не путали его с «==»).
Использование оператора присваивания
Основные причины, по которым может понадобиться присваивание:
- Нужно задать значение по умолчанию для вопроса, который не принимает значения по умолчанию через уравнение (например, список с переключателями: интерфейс позволяет выбрать один из вариантов ответа, но не позволяет ввести уравнение). Будьте осторожны: LimeSurvey не сможет проверить, что ваше уравнение возвращает один из допустимых ответов для этого вопроса.
- Нужно принудительно изменить ответ на предыдущий вопрос на основе более позднего ответа
- Нужна сложная квота: используйте один скрытый вопрос с одиночным выбором для квоты и задавайте его значение с помощью присваивания
- и т. д.
Для этой цели можно использовать всю систему Expression Manager.
Несколько примеров:
- Записать ответ на вопрос с коротким текстом в нижнем регистре:
{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, некоторые возможности Expression Manager использовать нельзя:
- открывать 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>
Доступ к переменным
Expression Manager предоставляет доступ только для чтения к любым нужным переменным. Для обратной совместимости доступны следующие:
- TOKEN:xxx - значение TOKEN (например, TOKEN:FIRSTNAME, TOKEN:ATTRIBUTE_5) (только для НЕанонимных опросов).
- QCODE.shown - отображаемое значение ответа (например, "Да") при использовании Expression Manager.
- Все значения {XXX}, используемые шаблонами.
- В тексте вопроса можно использовать {QID}, которое заменяется на ID вопроса, и {SGQ}, которое заменяется на SGQA вопроса.
Кроме того, Expression Manager позволяет обращаться к переменным по коду вопроса (столбец 'title' в таблице вопросов в базе данных). Это также метка переменной, используемая при экспорте данных в SPSS, R или SAS. Например, если у вас есть вопросы об имени, возрасте и поле, переменные можно назвать name, age и gender вместо 12345X13X22, 12345X13X23 и 12345X13X24. Так уравнения проще читать и проверять их логику всем участникам, а также можно перемещать вопросы, не отслеживая номера групп или вопросов.
Важно: безопасно обращаться только к переменным, которые находятся на предыдущих страницах или в предыдущих вопросах.
Кроме того, Expression Manager позволяет получить доступ ко многим свойствам вопроса:
| Синтаксис | Значение | Пример | Результат примера |
|---|---|---|---|
| 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 вместо >=
Именование переменных Qcode
Ниже описано, как составить Qcode (и получить доступ к некоторым свойствам) для каждого типа вопроса. В общем случае Qcode строится так:
QuestionCode . '_' . SubQuestionCode . '_' . ScaleId
Код подвопроса и ID шкалы используются или нет в зависимости от типа вопроса - см. таблицу ниже. Для комментария и другого коды вопроса: 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'} |
Зарезервированные переменные 'this', 'self' и 'that'
Часто нужно обработать все части вопроса, например посчитать, на сколько подвопросов дан ответ, или сложить баллы. В других случаях нужно обработать только определенные строки или столбцы вопроса (например, получить суммы по строкам или столбцам и сохранить их в базе данных). Эти зарезервированные переменные делают это достаточно простым.
Переменная 'this' используется только в расширенных настройках вопроса «Уравнение для проверки всего вопроса» и «Уравнение для проверки подвопроса». Она раскрывается в имена переменных каждой ячейки этих вопросов. Так, если нужно убедиться, что каждое значение больше трех, задайте «Уравнение для проверки подвопроса» как (this > 3).
Переменные 'self' и 'that' более мощные и работают как макросы, которые раскрываются перед обработкой уравнений. Возможные варианты синтаксиса:
- self
- self.суффикс
- self.подселектор
- self.подселектор.суффикс
суффикс - любой из обычных суффиксов qcode (например, NAOK, value, shown)
подселектор - один из следующих:
- 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 ....
- nosq_X - где X - идентификатор строки или столбца. Выбираются только подвопросы, не соответствующие шаблону X. Обратите внимание: поиск выполняется по полному коду-идентификатору, затем nosq_X совпадает с подвопросами и не включает подвопросы nX, X, Xn
Примеры:
- Дан ли ответ на какую-либо часть вопроса? {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' похожа на 'self', но позволяет обращаться к другим вопросам. Ее синтаксис:
- that.qname
- that.qname.суффикс
- that.qname.подселектор
- that.qname.подселектор.суффикс
qname - имя вопроса без каких-либо расширений подвопросов. Например, если вы создали вопрос 'q1', это и есть его qname
Примеры:
- Дан ли ответ на какую-либо часть вопроса q1? {count(that.q1.NAOK)>0}
- Каков балл оценки ответов для q2? {sum(that.q2.NAOK)}
- Каков общий итог для q3? {sum(that.q3.NAOK)}
- Какова сумма строки C в q4? {sum(that.q4.sq_C.NAOK)}
- Какова сумма столбца 2 в q4? {sum(that.q4.sq_2.NAOK)}
Переменные 'self' и 'that' можно использовать в любом условии, проверке или подстановке.
Есть одна оговорка: при использовании функции Show Logic File будет показано раскрытое значение 'self' и 'that'. Это позволяет увидеть фактическое уравнение, которое будет сгенерировано, чтобы вы (и Expression Manager) могли убедиться, что переменные существуют. Это может сбивать с толку, поскольку уравнения могут оказаться очень длинными. Однако если открыть вопрос для редактирования, вы увидите исходное уравнение с 'self' и/или 'that'.
Также учтите, что не следует использовать эти переменные, если (а) вы хотите явно указать имя каждой переменной, используемой в уравнении, или (б) используете переменные, у которых нет подвопросов (например, вопросы с одним ответом). В таких случаях префикс 'that' избыточен, и вы рискуете получить неожиданные результаты.
Использование NAOK
NAOK --> "Not Applicable" (NA) is alright (OK) — «неприменимо» допустимо
Использование NAOK означает, что все переменные или некоторые из них нерелевантны (т. е. «Not Applicable» (NA) допустимо (OK)).
Если вы используете в любом уравнении переменную из вопроса, и этот вопрос (или подвопрос) скрыт по условию, то всё уравнение отключается.
Например: count(Q1_SQ1,Q1_SQ2,Q1_SQ3,Q1_SQ4) всегда возвращает пустую строку, если один из подвопросов Q1 отфильтрован. Чтобы подсчитать количество отмеченных подвопросов в таком вопросе, можно использовать count(Q1_SQ1.NAOK,Q1_SQ2.NAOK,Q1_SQ3.NAOK,Q1_SQ4.NAOK). Если подвопрос скрыт, Expression Manager возвращает пустую строку.
Без NAOK: если один вопрос или один подвопрос скрыт, Expression Manager всегда возвращает пустую строку, что равнозначно значению false.
Суффикс .shown всегда использует систему NAOK (пустая строка, если вопрос скрыт), но если вам нужен код ответа, всегда лучше добавлять .NAOK после кода вопроса. Исключение — случаи, когда вам это нужно и вы понимаете, что делаете.
Другой пример и дополнительная информация приведены в разделе Overriding Cascading Conditions
Доступ к функциям
Expression Manager предоставляет доступ к математическим, строковым и пользовательским функциям, как показано ниже. Для этих функций есть эквиваленты на 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 | Форматировать дату/время по Гринвичу | 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 (Updated in 3.0.2 ) | В стиле 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 | Определяет, является ли тип переменной 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 ) | Вернуть список указанного свойства вопроса (retProp) с разделителем 'glue' для вопросов из списка, удовлетворяющих критерию (cmpProp op value) | string listifop(cmpProp, op, value, retProp, glue, sgqa1, sgqa2, ... sgqaN) |
| log | Логарифм числа по основанию base, если оно задано, или натуральный логарифм. | number log(number,base=e) |
| 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 | Функция modulo пока не поддерживается. Вместо нее можно использовать функцию floor() | floor(x/y)==(x/y) |
| nl2br | Вставляет HTML-разрывы строк перед всеми переводами строки в строке | string nl2br(string) |
| number_format | Форматировать число путем разделения по три цифры | string number_format(number) |
| 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 | Возвращает значение true, если все заполненные ответы уникальны | boolean unique(arg1, ..., argN) |
Планируемые функции и функции, находящиеся на рассмотрении
Другие функции, которые планируются (или рассматриваются), но пока не реализованы, перечислены ниже. Некоторые из них нужны для обратной совместимости с другим инструментом для проведения опросов.
| Синтаксис | Значение | Комментарии |
|---|---|---|
| e() | возвращает значение e | |
| formatDate(X,PAT) | возвращает строковое значение даты X, отформатированное по шаблону формата даты Java PAT | |
| formatNumber(X,PAT) | возвращает строковое значение числа X, отформатированное по шаблону формата чисел Java PAT | |
| getAnsOption(X) | Основной плагин ExpressionAnswerOptions | |
| getAnsOption(X,Y) | Основной плагин ExpressionAnswerOptions | |
| getRelevance(X) | возвращает уравнение условия для вопроса X | |
| getStartTime() | возвращает дату, соответствующую системному времени начала интервью | |
| getType(X) | возвращает строковое название типа данных, например *NA*, если isNA() | |
| gotoFirst() | переходит к первому набору подходящих вопросов — это нарушает обычный ход работы системы | |
| gotoNext() | переходит к следующему набору подходящих вопросов — это нарушает обычный ход работы системы | |
| gotoPrevious() | переходит к предыдущему набору подходящих вопросов — это нарушает обычный ход работы системы | |
| isAsked(X) | возвращает true, если ответ не относится к типам *NA*, *INVALID* и *UNASKED* | |
| isInvalid(X) | возвращает true, если ответ имеет тип *INVALID* | |
| isNA(X) | возвращает true, если ответ имеет тип *NA* | |
| isNotUnderstood(X) | возвращает true, если ответ имеет тип *HUH* | |
| isRefused(X) | возвращает true, если ответ имеет тип *REFUSED* | |
| isSpecial(X) | возвращает true, если ответ имеет тип *UNASKED*, *NA*, *REFUSED*, *INVALID*, *UNKNOWN* или *HUH* | |
| isUnknown(X) | возвращает true, если ответ имеет тип *UNKNOWN* | |
| jumpTo(X) | переходит к группе, содержащей указанный вопрос — это нарушает обычный ход работы системы | |
| jumpToFirstUnasked() | переходит к первому незаданному вопросу |
|
| lastIndexOf(X,Y) | возвращает последний индекс (с нуля) строки Y в строке X. Возвращает -1, если Y не содержится в X | |
| list(X,...) | строка со списком положительных значений через запятую, где последние два значения разделены союзом «и» | |
| mean(X,...) | возвращает среднее значение списка значений | |
| numAnsOptions(X) | возвращает количество вариантов ответов вопроса X | |
| orlist(X,...) | строка со списком положительных значений через запятую, где последние два значения разделены союзом «или» | |
| parseDate(X,PAT) | возвращает значение даты из строки X, разобранной по шаблону формата даты Java PAT | |
| parseNumber(X,PAT) | возвращает числовое значение из строки X, разобранной по шаблону формата чисел Java PAT | |
| showAllResponsesExcept( questionList,attributeList,attributeTitleList) | questionList — список идентификаторов вопросов, разделённых вертикальной чертой; attributeList — список атрибутов, разделённых вертикальной чертой (например, номер вопроса, заголовок, текст, тип — так вы можете выбрать, что показывать); attributeTitleList — список заголовков таблицы, разделённых вертикальной чертой, чтобы можно было локализовать отчёт. | |
| showTheseResponses( questionList,attributeList,attributeTitleList) | questionList — список идентификаторов вопросов, разделённых вертикальной чертой; attributeList — список атрибутов, разделённых вертикальной чертой (например, номер вопроса, заголовок, текст, тип — так вы можете выбрать, что показывать); attributeTitleList — список заголовков таблицы, разделённых вертикальной чертой, чтобы можно было локализовать отчёт. |
Expression Manager знает, какие переменные являются локальными
Чтобы правильно построить JavaScript для страницы, Expression Manager должен знать, какие переменные заданы на странице и каков их идентификатор в JavaScript (например, для 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» (означающий, что «Не применимо» допустимо). В таком случае поведение будет следующим. Допустим, у вас есть переменная q1.NAOK
- q1 не добавляется в выражение LEManyNA()
- LEMval('q1') по-прежнему будет проверять, актуален ли ответ, и вернёт '', если нет (поэтому отдельные неактуальные ответы будут игнорироваться, но не сделают недействительным всё выражение).
Таким образом, решение проблемы текущей суммы — использовать уравнение sum(q1.NAOK,q2.NAOK,q3.NAOK,...,qN.NAOK).
Использование суффикса .NAOK также позволяет авторам создавать опросы с несколькими возможными путями, которые затем сходятся на общих путях. Например, предположим, что респонденты отвечают на вопросы опроса так, что их ответы выходят за пределы нормального диапазона. Автор может предупредить их, что результаты могут оказаться недостоверными, и спросить, действительно ли они хотят продолжить опрос. Если они отвечают «Да», будут показаны остальные вопросы. Условие для «остальных вопросов» проверяло бы, были ли первоначальные ответы в пределах нормального диапазона, ИЛИ ответил ли респондент «Да» на вопрос, который актуален, только если ответы вышли за пределы нормы.
Как Expression Manager поддерживает условную микро-адаптацию текста?
Вот пример микро-адаптации текста (где Question Type=='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 | yesno | {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() в последнем вопросе будет динамически обновляться на странице.
Expression Manager реализует эту функциональность, оборачивая каждое выражение в именованный элемент <span>. При каждом изменении значения он пересчитывает выражение, которое должно отображаться в этом элементе <span>, и обновляет отображение. На одной странице может быть десятки и даже сотни таких адаптированных выражений, и страница отобразит их все за одно обновление экрана.
Соответствие функций LimeSurvey 1.91+ и Expression Manager
| Прежняя функция | Новая функция | Комментарии |
|---|---|---|
| Условия | Условие | Вы можете использовать очень сложные условные уравнения и получать доступ к более широкому набору переменных |
| Оценка ответов | Уравнение | Любые оценочные баллы можно преобразовать в уравнение. Это гарантирует запись балла в базу данных, а также позволяет видеть динамические изменения значения балла на текущей странице |
| Подстановки | Expression Manager | Основной механизм принимает входную строку и рассматривает всё, что находится в фигурных скобках, как выражение, поэтому он обрабатывает все прежние типы подстановок. Чтобы не нарушить встроенный JavaScript, Expression Manager обрабатывает содержимое в фигурных скобках, только если (а) внутри фигурных скобок нет пробелов в начале или в конце — например, {expr} является выражением, а { expr}, {expr } и { expr } — нет. Кроме того, Expression Manager не обрабатывает содержимое внутри собственных строк (например, {list('hi','there {braces}')} выдаёт «hi there {braces}»). Он также игнорирует экранированные фигурные скобки (например, \{this is not an expression\}) |
| Проверка | Expression Manager | Планируется взять текущие атрибуты вопросов min/max и обрабатывать их с помощью Expression Manager. Тогда сами значения min/max смогут быть выражениями |
Подсветка синтаксиса
Чтобы упростить ввод и проверку выражений, EM предоставляет подсветку синтаксиса со следующими возможностями:
Типы и значения подсветки синтаксиса
| Цвет | Пример | Значение | Подсказка | Комментарии |
|---|---|---|---|---|
| коричневатый фон | Пример | всё уравнение | нет | Всё, что заключено в фигурные скобки и распознано как уравнение (например, нет пробелов в начале и в конце), выделяется коричневатым фоном, чтобы отличать его от окружающего текста |
| жирный красный текст | Пример | ошибка | Пояснение к ошибке | Может быть неизвестная переменная или ошибка в функции и т. д. Опрос может полностью перестать работать, но это не отображается для обычных пользователей. |
| синий текст | Пример | имя функции | значение и допустимый синтаксис | имена функций, а также всё, что должно быть функцией, так как за ним следует открывающая скобка, отображаются жирным синим текстом. Подсказки показывают значение и допустимый синтаксис функции. |
| серый текст | Пример | строка | нет | строки в одинарных и двойных кавычках отображаются серым текстом |
| голубой текст | Пример | переменная, заданная на той же странице | [имя или код SGQA]: вопрос; значение; список ответов с кодами для каждого значения | Любая переменная, заданная на той же странице, что и текущий вопрос, отображается голубым текстом, что означает, что она может обновляться в JavaScript. Подсказка показывает её имя, сам вопрос и текущее значение (или пусто, если значение не задано). Если для типа вопроса ожидаются ответы из перечисляемого набора значений, показывается соответствие кодов отображаемым значениям. |
| зелёный текст | Пример | переменная, заданная на предыдущей странице | [имя или код SGQA]: вопрос; значение; список ответов с кодами для каждого значения | Любая переменная, заданная на предыдущей странице, отображается жирным зелёным текстом. Подсказка показывает её имя, сам вопрос и текущее значение (или пусто, если значение не задано). Если для типа вопроса ожидаются ответы из перечисляемого набора значений, показывается соответствие кодов отображаемым значениям. |
| жирный розовый текст | Пример | переменная, заданная на одной из следующих страниц как правило, в начале опроса она пуста, но может быть заполнена через индекс или при переходе назад | [имя или код SGQA]: вопрос; значение; список ответов с кодами для каждого значения | Любая переменная, заданная на одной из следующих страниц, отображается жирным розовым текстом. Подсказка показывает её имя, сам вопрос и текущее значение (или пусто, если значение не задано). Если для типа вопроса ожидаются ответы из перечисляемого набора значений, показывается соответствие кодов отображаемым значениям. |
| жирный коричневый текст | Пример | значение подстановки Lime | значение | Строки подстановки Lime (например, {TOKEN:xxx}, {PRIVACY_MESSAGE}) отображаются жирным коричневым текстом. |
| красный текст | Пример | оператор присваивания (=) | предупреждающее сообщение | Если вы используете оператор присваивания (=), он отображается красным текстом. Это помогает избежать случайного переназначения значений, когда вы на самом деле хотели проверить, равно ли a == b, а не задать значение a = b. |
| обычный чёрный текст | Пример | знаки препинания | нет | Все остальные знаки препинания внутри выражения отображаются обычным чёрным текстом. |
| текст в красной рамке | жирная красная линия обводит ошибку | синтаксическая ошибка | описание ошибки | Все обнаруженные синтаксические ошибки выделяются красной рамкой. Подсказка показывает ошибку. Примеры: незакрытые скобки, использование неопределённых функций, передача неверного числа аргументов функциям, плохо структурированные выражения (например, отсутствуют операторы между переменными), попытка присвоить новое значение переменной только для чтения, попытка присвоить значения не переменным или использование неподдерживаемого синтаксиса. Обратите внимание, что система обнаружения синтаксических ошибок может сообщить только об одной ошибке в выражении, даже если их несколько; однако если ошибки обнаружены, будет показана как минимум одна. |
Дополнительная литература
Примеры опросов Expression Manager
Сценарии использования и инструкции
Пошаговые примеры
Статистические функции Expression Manager
Справочник для разработчиков










