Практическо ръководство за писане на CEL изрази в CMS.
Практическо ръководство за писане на CEL изрази в CMS.
CEL (Common Expression Language) е лек скриптов език, вграден в нашата CMS. Той ви позволява да пишете динамични изрази, които извличат данни от документи, четат параметри от URL адреси и изчисляват стойности в движение.
Ето какво се случва, когато се изпълни CEL скрипт:
Вашият скрипт Енджинът Резултат
| | |
v v v
documents.get("article", "intro") --> Извлича от базата данни --> { headline: "Welcome", body: "..." }
.headline --> Извлича полето --> "Welcome"
Мислете за CEL като за език за заявки само за четене. Той не може да променя нищо в базата данни — само чете данни и връща изчислен резултат. Това го прави безопасен за използване навсякъде в CMS.
Всеки CEL израз има достъп до три неща:
| Обект | Какво представлява | Пример |
|---|---|---|
documents | Извлича всеки документ от CMS | documents.get("country", "us") |
meta | Информация за текущата заявка (локал, URL параметри) | meta.locale, meta.params.slug |
schema | Дефинициите на полетата на текущия документ | schema.fields |
docКогато пишете CEL изрази в редактора на документ, можете да получите достъп до стойностите на полетата на текущия документ чрез обекта doc. Това позволява изчисляеми полета и препратки между полета.
// Достъп до полето за цена на текущия документ
doc.price
// Изчисляване на общата стойност от полетата на текущия документ
doc.price * doc.quantity
// Условие според статуса на текущия документ
doc.status == "published" ? doc.title : "Чернова: " + doc.title
Обектът doc съдържа всички стойности на полетата от редактирания документ. Той е полезен за:
doc.price * doc.quantity)Най-мощната функция на CEL е извличането на документи от всяко място във вашата CMS.
Синтаксис: documents.get(schemaName, identifier)
Да предположим, че имате документ article, съхранен с идентификатор "welcome-post":
// Съхранено в CMS като: article / welcome-post
{
"headline": "Добре дошли в нашата платформа",
"author": "Sarah Chen",
"body": "Радваме се да обявим...",
"tags": ["announcement", "news"]
}
За да извлечете целия документ:
documents.get("article", "welcome-post")
Връща: целия документ.
За да извлечете само заглавието:
documents.get("article", "welcome-post").headline
Връща: "Добре дошли в нашата платформа"
За да извлечете автора:
documents.get("article", "welcome-post").author
Връща: името на автора.
Когато страницата ви има динамични маршрути (например /articles/[slug]), можете да използвате meta.params, за да получите URL параметъра и да извлечете правилния документ.
Ако някой посети /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Връща: заглавието на статията.
Така се изграждат динамични страници — един и същ CEL скрипт работи за всяка статия, като използва съответния slug от URL адреса.
Синтаксис: documents.find(schemaName) или documents.find(schemaName, filter)
// Получаване на всички държави
documents.find("country")
Връща:
[
{ "code": "us", "name": "Съединени щати", "flag": "US" },
{ "code": "sa", "name": "Саудитска Арабия", "flag": "SA" },
{ "code": "gb", "name": "Обединеното кралство", "flag": "GB" }
]
// Получаване на държави с филтър
documents.find("country", { "where": { "code": "us" } })
CEL поддържа извличане на преведено съдържание по два начина: автоматичен превод според локала и явно търсене на превод.
Когато meta.locale е зададен (например чрез параметри на маршрута или предпочитанията на потребителя), documents.get() автоматично обединява преведеното съдържание:
// Ако meta.locale е "fr", връща френския превод, обединен с основния документ
documents.get("greeting", "welcome").headline
Как работи:
meta.locale не е "en" или "en-US", търси превод в таблицата translations{ ...baseContent, ...translatedContent }Това означава, че преведените полета заменят основните, а непреведените полета използват стойностите от основния документ.
Когато трябва да извлечете конкретен превод независимо от текущия локал:
Синтаксис: documents.translated(schemaName, identifier, locale)
// Винаги извлича испанския превод
documents.translated("greeting", "welcome", "es").headline
// Извлича превод според параметър от URL
documents.translated("product", meta.params.id, meta.params.lang).description
// Сравнява преводи
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title
Документи за поздрав с преводи:
// Основен документ: greeting / welcome
{ "headline": "Добре дошли", "subheadline": "Добре дошли в нашата платформа" }
// Превод (език: "fr")
{ "headline": "Bienvenue", "subheadline": "Bienvenue sur notre plateforme" }
// Превод (език: "es")
{ "headline": "Bienvenido", "subheadline": "Bienvenido a nuestra plataforma" }
CEL скриптове:
// С meta.locale = "fr"
documents.get("greeting", "welcome").headline
// Връща: "Bienvenue"
// Явен испански превод
documents.translated("greeting", "welcome", "es").headline
// Връща: "Bienvenido"
// Шаблон с резервна стойност при липсващ превод
documents.translated("greeting", "welcome", meta.params.lang) != null
? documents.translated("greeting", "welcome", meta.params.lang).headline
: documents.get("greeting", "welcome").headline
Имате hero-block, който трябва да показва заглавие, извлечено от документ article.
CEL скриптът в полето за заглавие на hero блока е:
documents.get("article", "homepage-hero").headline
Резултат: Hero блокът показва заглавието от документа.
Изграждате страница на /countries/[code] и искате да покажете пълното име на държавата.
documents.get("country", meta.params.code).name
Когато някой посети /countries/us:
meta.params.code = "us""Съединени щати"Показване на различни заглавия според локала на потребителя.
meta.locale == "ar-SA" ? "Добре дошли на всички" : "Добре дошли"
Ако вашият article има поле countryCode, можете да получите пълното име на държавата:
documents.get("country", documents.get("article", "us-news").countryCode).name
CEL изпълнява вложеното търсене, извлича countryCode, след което използва получената стойност за намиране на държавата.
Ако даден документ може да не съществува, можете да зададете резервна стойност:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Статията не е намерена"
Можете да проверите и дали съществува конкретно поле:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Неизвестен автор"
Проверка дали статията има определен етикет:
"featured" in documents.get("article", "welcome-post").tags
Връща: true, ако статията има етикета featured.
Получаване на първия етикет:
documents.get("article", "welcome-post").tags[0]
Броене на етикетите:
size(documents.get("article", "welcome-post").tags)
Параметричните маршрути са ключът към изграждането на динамични, локализирани страници. Когато дефинирате шаблон на маршрут като /{lang}/landingPage, CMS извлича параметрите от URL адреса и ги предоставя чрез meta.params.
Маршрутите използват синтаксиса :paramName или {paramName} за дефиниране на динамични сегменти:
| Шаблон | Примерен URL | Извлечени параметри |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Всеки параметър на маршрута може да бъде свързан със схема на документ за валидация:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Това указва на CMS да:
lang от URL адресаlanguagedocuments.get("greeting", meta.params.lang).headline
| URL | meta.params.lang | Резултат |
|---|---|---|
/ko/landingPage | "ko" | заглавие на корейски |
/en/landingPage | "en" | заглавие на английски |
/ja/landingPage | "ja" | заглавие на японски |
За маршрути като /{country}/{lang}/products:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
// Получаване на името на държавата
documents.get("country", meta.params.country).name
// Получаване на локализиран списък с продукти според държавата
documents.find("product", { "where": { "country": meta.params.country } })
// Комбинирано: поздрав според държавата и езика на потребителя
documents.get("greeting", meta.params.lang).headline + " от " + documents.get("country", meta.params.country).name
CMS валидира параметрите йерархично:
country спрямо схемата countrylang спрямо схемата languagelang присъства в масива country.languages[]meta.segments предоставя необработения път на URL адреса като масив. Това е полезно, когато ви е необходим достъп по позиция без именувани параметри.
| Път на URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Случай на употреба | Най-добър подход |
|---|---|
| Именувани параметри от шаблона на маршрута | meta.params.lang |
| Достъп по позиция | meta.segments[0] |
| Получаване на дълбочината на пътя | size(meta.segments) |
| Проверка дали пътят съдържа сегмент | "admin" in meta.segments |
// Получаване на първия сегмент
meta.segments[0]
// Проверка на дълбочината на пътя
size(meta.segments) > 2 ? "дълбок" : "плитък"
// Проверка дали сме в административната секция
"admin" in meta.segments ? "административен режим" : "публичен режим"
// Резервен вариант: използване на сегмент, ако параметърът не е свързан
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
Обектът meta съдържа целия контекст на текущата заявка:
| Свойство | Тип | Описание |
|---|---|---|
meta.locale | string | Текущ код на локала (напр. "en-US", "ko-KR", "ar-SA") |
meta.params | Record<string, string> | Параметри на маршрута, извлечени от URL шаблона |
meta.segments | string[] | URL пътят, разделен на сегменти |
meta.docId | string | null | UUID на текущия документ (null за нови документи) |
meta.title | string | Заглавие на текущия документ |
Кодът на локала следва формата BCP 47 (език-регион):
// Проверка за езици с писане отдясно наляво
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Получаване само на езиковата част
meta.locale.split("-")[0] // Не се поддържа — използвайте meta.params.lang
Параметрите на маршрута винаги са низове. CMS ги валидира спрямо свързаните схеми преди оценяването:
// Достъп до именуван параметър
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Проверка дали параметърът съществува
has(meta.params.category) // true/false
// Използване при извличане на документ
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Необработените URL сегменти като масив:
meta.segments[0] // Първи сегмент
meta.segments[1] // Втори сегмент
size(meta.segments) // Брой сегменти
"products" in meta.segments // Съдържа ли пътят "products"?
UUID на текущия документ, полезен за саморефериращи се скриптове:
meta.docId != null ? "редактиране" : "създаване на нов документ"
meta.docId != null ? documents.get("article", meta.docId).status : "чернова"
Заглавието на текущия документ:
"Редактиране: " + meta.title
meta.title.contains("Draft") ? "в процес на работа" : "публикувано"
За по-чист синтаксис, когато схемата е известна, но идентификаторът е динамичен:
// Традиционен подход
documents.get("airports", meta.params.code).name
// С ref() — схемата е отделена от динамичния идентификатор
documents.ref("airports").get(meta.params.code).name
И двата подхода са еквивалентни, но ref() прави динамичната част по-ясна.
documents.get("schema", "identifier") // Получаване на един документ
documents.get("schema", "id").fieldName // Получаване на конкретно поле
documents.find("schema") // Получаване на всички документи
documents.find("schema", { "where": {...}}) // Заявка с филтър
documents.ref("schema").get(identifier) // Свързано търсене
documents.translated("schema", "id", "fr") // Получаване с изрично зададен локал
meta.locale // "en-US", "ar-SA" и др.
meta.params.xyz // URL параметър с име "xyz"
meta.segments // URL път като масив: ["articles", "intro"]
meta.segments[0] // Първи сегмент от пътя
meta.docId // ID на текущия документ (или null)
meta.title // Заглавие на текущия документ
doc.fieldName // Стойност на поле от текущия документ (в контекст на редактора)
// Сравнение
== != < <= > >=
// Логика
&& || !
// Тернарен оператор (ако-иначе)
condition ? valueIfTrue : valueIfFalse
// Членство
"value" in listOrMap
size(list) // Брой елементи
size(string) // Дължина на низ
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Проверка дали свойството съществува
hasProperty(obj, "key") // Проверка дали обектът има ключ (алтернативен синтаксис)
Ако нещо се обърка, ще видите едно от следните съобщения:
| Грешка | Какво означава |
|---|---|
SYNTAX_ERROR | Правописна грешка в скрипта (липсваща кавичка, неправилен оператор) |
TYPE_ERROR | Смесвате типове, които не могат да се използват заедно |
RUNTIME_ERROR | Скриптът се е изпълнил, но е срещнал проблем (неопределена променлива) |
FETCH_LIMIT_EXCEEDED | Извличате твърде много документи (максимум 50) |
TIMEOUT | Скриптът се изпълнява твърде дълго (максимум 5 секунди) |
AST_DEPTH_EXCEEDED | Изразът е прекалено дълбоко вложен (максимална дълбочина: 50) |
SCRIPT_TOO_LONG | Скриптът надвишава ограничението от 5000 знака |
CEL енджинът е проектиран с възможност за разширяване. Планираните бъдещи възможности включват:
// Бъдеще: извикване на външни услуги чрез MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Бъдеще: генериране на съдържание с AI
ai.summarize(documents.get("article", meta.params.id).body, 100)
ai.translate(meta.params.text, meta.params.targetLang)
ai.classify(meta.params.input, ["positive", "negative", "neutral"])
Тези възможности ще бъдат добавени чрез системата за регистрирани функции, като се запази обратната съвместимост със съществуващите скриптове.
documents. или meta. и редакторът ще покаже наличните опцииdocuments.get("schema", "id"), след което добавете .fieldName!= null ? ... : ...documents.get() или documents.find() се брои към лимита от 50 извличанияhas(meta.params.category) преди достъпТози раздел обхваща разширени шаблони за свързване на документи и изграждане на релационни структури от съдържание.
Най-простата форма е документ да препраща към друг чрез идентификатор.
// Статията съхранява ID на автора и извлича името му
documents.get("author", documents.get("article", "intro").authorId).name
// Традиционен подход
documents.get("country", documents.get("airport", meta.params.code).countryCode).name
// С ref() — по-ясно, когато схемата е известна, но идентификаторът е динамичен
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name
// Летище → Държава → Регион → Континент
documents.get("continent",
documents.get("region",
documents.get("country",
documents.get("airport", meta.params.code).countryCode
).regionCode
).continentCode
).name
// Получаване на локализирано име на държавата за летище
documents.translated("country",
documents.get("airport", meta.params.code).countryCode,
meta.params.lang
).name
Документът съхранява ID, препращащо към друг документ.
// Разрешаване на името на автора
documents.get("author", documents.get("article", meta.params.slug).authorId).name
// Разрешаване на категория с резервна стойност
documents.get("article", meta.params.slug).categoryId != null
? documents.get("category", documents.get("article", meta.params.slug).categoryId).name
: "Без категория"
Документите препращат един към друг чрез смислови кодове вместо UUID.
// Верига Летище → Държава → Валута
documents.get("currency",
documents.get("country",
documents.get("airport", meta.params.code).countryCode
).currencyCode
).symbol
Използвайте doc за изчисляеми полета, които препращат към други документи въз основа на стойностите на текущия документ.
// В документ за продукт: извличане на данни за свързаната категория
documents.get("category", doc.categoryId).description
// Изчисляване на разхода за доставка според държавата на произход
documents.get("shipping-rates", doc.originCountry).baseRate * doc.weight
Когато документите препращат един към друг, внимавайте с лимитите за извличане.
documents.find("article", { "where": { "authorId": documents.get("article", meta.params.slug).authorId } })
Когато дадено поле може да препраща към различни схеми:
documents.get("content-block", "hero-1").sourceType == "article"
? documents.get("article", documents.get("content-block", "hero-1").sourceId).headline
: documents.get("product", documents.get("content-block", "hero-1").sourceId).name
Всяко извикване на documents.get(), documents.find() и documents.ref().get() се проследява за невалидиране на кеша. Когато рефериран документ се промени, CMS знае кои CEL изрази трябва да бъдат преизчислени.
Проследяваните зависимости включват:
schema:identifier — зависимост от конкретен документschema:identifier — същото като get, чрез свързан синтаксисschema:* — зависимост на ниво схема (всеки документ в схемата)Това ръководство създава многоезична целева страница, достъпна на /{lang}/landingPage.
В административния панел на CMS създайте персонализирана схема с име greeting.
Създайте документи за всеки език, например greeting/ko, greeting/en и greeting/ja.
Създайте страница със следната конфигурация:
/{lang}/landingPagelang с компонента language{
"lang": "language"
}
Добавете hero блок към маршрута със следните CEL скриптове:
// Заглавие
documents.get("greeting", meta.params.lang).headline
// Подзаглавие
documents.get("greeting", meta.params.lang).subheadline
// Текст на CTA
documents.get("greeting", meta.params.lang).ctaText
// URL адрес на CTA
documents.get("greeting", meta.params.lang).ctaUrl
Добавете catch-all маршрут. ParametricRoutePage разрешава страницата, извлича meta.params от URL адреса, оценява CEL свързванията от страна на сървъра и визуализира всеки блок чрез вашия registry — не е необходимо сами да изграждате контекста meta или да извиквате нискоуровневия клиент.
Посетете следните URL адреси, за да видите локализираното съдържание:
| URL | Очаквано заглавие |
|---|---|
/ko/landingPage | 환영 |
/en/landingPage | Добре дошли |
/ja/landingPage | いらっしゃいませ |
Когато потребител посети /ko/landingPage:
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) се разрешават до корейско съдържаниеinterface CelMeta {
/** Текущ код на локала (напр. 'en-US') */
locale: string;
/** Параметри на маршрута, извлечени от URL */
params: Record<string, string>;
/** Сегменти на URL пътя */
segments: string[];
/** ID на текущия документ (ако се редактира съществуващ документ) */
docId: string | null;
/** Заглавие на текущия документ */
title: string;
}
Функцията extractParams обработва пътищата на URL адресите:
Шаблон: /{country}/{lang}/products
Път: /us/en/products
Алгоритъм:
1. Нормализирайте и двата пътя (премахнете крайните наклонени черти)
2. Разделете ги на сегменти
3. Съпоставете броя на сегментите (трябва да е равен)
4. За всяка двойка сегменти:
- Ако шаблонът започва с : или {}, извлечете параметъра
- В противен случай сегментите трябва да съвпадат точно
5. Върнете: { country: "us", lang: "en" }
// Просто свързване (използва полето "code" за търсене)
{ "lang": "language" }
// Подробно свързване (персонализирано поле за slug)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
}
При извличане чрез documents.get(schema, identifier):
idcontent.codecontent.slugtitleТова позволява гъвкави препратки към документи чрез всеки уникален идентификатор.