Praktyczny przewodnik po pisaniu wyrażeń CEL w systemie CMS.
Praktyczny przewodnik po pisaniu wyrażeń CEL w systemie CMS.
CEL (Common Expression Language) to lekki język skryptowy wbudowany w nasz system CMS. Umożliwia tworzenie dynamicznych wyrażeń, które pobierają dane z dokumentów, odczytują parametry adresu URL i obliczają wartości w locie.
Co dzieje się podczas uruchamiania skryptu CEL:
Twój skrypt Silnik Wynik
| | |
v v v
documents.get("article", "intro") --> Pobiera z bazy danych --> { headline: "Welcome", body: "..." }
.headline --> Wyodrębnia pole --> "Welcome"
Możesz myśleć o CEL jak o języku zapytań tylko do odczytu. Nie może modyfikować niczego w bazie danych — jedynie odczytuje dane i zwraca obliczony wynik. Dzięki temu można go bezpiecznie używać w dowolnym miejscu systemu CMS.
Każde wyrażenie CEL ma dostęp do trzech elementów:
| Obiekt | Czym jest | Przykład |
|---|---|---|
documents | Pobiera dowolny dokument z systemu CMS | documents.get("country", "us") |
meta | Informacje o bieżącym żądaniu (lokalizacja, parametry URL) | meta.locale, meta.params.slug |
schema | Definicje pól bieżącego dokumentu | schema.fields |
docPodczas pisania wyrażeń CEL w edytorze dokumentu możesz uzyskać dostęp do wartości pól bieżącego dokumentu za pomocą obiektu doc. Umożliwia to tworzenie pól obliczanych i odwołań między polami.
// Dostęp do pola ceny bieżącego dokumentu
doc.price
// Obliczenie sumy na podstawie pól bieżącego dokumentu
doc.price * doc.quantity
// Warunek zależny od statusu bieżącego dokumentu
doc.status == "published" ? doc.title : "Draft: " + doc.title
Obiekt doc zawiera wszystkie wartości pól edytowanego dokumentu. Jest przydatny w przypadku:
doc.price * doc.quantity),Najpotężniejszą funkcją CEL jest możliwość pobierania dokumentów z dowolnego miejsca w systemie CMS.
Składnia: documents.get(schemaName, identifier)
Załóżmy, że masz dokument article zapisany z identyfikatorem "welcome-post".
// Zapisany w CMS jako: article / welcome-post
{
"headline": "Welcome to Our Platform",
"author": "Sarah Chen",
"body": "We're excited to announce...",
"tags": ["announcement", "news"]
}
Aby pobrać cały dokument:
documents.get("article", "welcome-post")
Zwraca: cały dokument.
Aby pobrać tylko nagłówek:
documents.get("article", "welcome-post").headline
Zwraca: "Welcome to Our Platform"
Aby pobrać autora:
documents.get("article", "welcome-post").author
Zwraca: "Sarah Chen"
Gdy strona ma dynamiczne trasy, takie jak /articles/[slug], możesz użyć meta.params, aby pobrać parametr URL i właściwy dokument.
Jeśli ktoś odwiedzi /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Zwraca: "Welcome to Our Platform"
W ten sposób tworzysz dynamiczne strony — ten sam skrypt CEL działa dla dowolnego artykułu, wykorzystując wartość slug znajdującą się w adresie URL.
Składnia: documents.find(schemaName) lub documents.find(schemaName, filter)
// Pobierz wszystkie kraje
documents.find("country")
Zwraca: listę dokumentów.
// Pobierz kraje z filtrem
documents.find("country", { "where": { "code": "us" } })
CEL obsługuje pobieranie przetłumaczonej treści dokumentów na dwa sposoby: automatyczne tłumaczenie na podstawie lokalizacji oraz jawne wyszukiwanie tłumaczenia.
meta.localeGdy ustawiono meta.locale (np. na podstawie parametrów trasy lub preferencji użytkownika), documents.get() automatycznie scala przetłumaczoną treść:
// Jeśli meta.locale ma wartość "fr", zwraca francuskie tłumaczenie scalone z dokumentem bazowym
documents.get("greeting", "welcome").headline
Jak to działa:
meta.locale nie ma wartości "en" ani "en-US", wyszukiwane jest tłumaczenie w tabeli translations.{ ...baseContent, ...translatedContent }.Oznacza to, że przetłumaczone pola zastępują pola bazowe, a nieprzetłumaczone korzystają z dokumentu bazowego.
documents.translated()Jeśli trzeba pobrać konkretne tłumaczenie niezależnie od bieżącej lokalizacji:
Składnia: documents.translated(schemaName, identifier, locale)
// Zawsze pobierz tłumaczenie hiszpańskie
documents.translated("greeting", "welcome", "es").headline
// Pobierz tłumaczenie na podstawie parametru URL
documents.translated("product", meta.params.id, meta.params.lang).description
// Porównaj tłumaczenia
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title
Masz blok hero-block, który powinien wyświetlać nagłówek pobrany z dokumentu article.
Skrypt CEL w polu tytułu bloku hero:
documents.get("article", "homepage-hero").headline
Wynik: blok hero wyświetla "Build Faster, Ship Smarter".
Tworzysz stronę /countries/[code] i chcesz wyświetlić pełną nazwę kraju.
documents.get("country", meta.params.code).name
Gdy ktoś odwiedzi /countries/us:
meta.params.code = "us""United States"Wyświetlaj różne nagłówki zależnie od lokalizacji użytkownika.
meta.locale == "ar-SA" ? "Welcome, everyone" : "Welcome"
Jeśli lokalizacja to "ar-SA", zwracane jest "Welcome, everyone"; w przeciwnym razie zwracane jest "Welcome".
documents.get("country", documents.get("article", "us-news").countryCode).name
Wewnętrzne wywołanie pobiera artykuł, .countryCode wyodrębnia kod kraju, kolejne wywołanie pobiera kraj, a .name wyodrębnia jego nazwę.
Jeśli dokument może nie istnieć, możesz podać wartość zapasową:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Article Not Found"
Możesz również sprawdzić, czy istnieje konkretne pole:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Unknown Author"
Sprawdź, czy artykuł ma określony tag:
"featured" in documents.get("article", "welcome-post").tags
Zwraca: true, jeśli artykuł ma tag "featured".
Pobierz pierwszy tag:
documents.get("article", "welcome-post").tags[0]
Policz tagi:
size(documents.get("article", "welcome-post").tags)
meta.paramsTrasy parametryczne są kluczem do tworzenia dynamicznych, lokalizowanych stron. Po zdefiniowaniu wzorca, takiego jak /{lang}/landingPage, CMS wyodrębnia parametry z adresu URL i udostępnia je za pomocą meta.params.
Trasy używają składni :paramName lub {paramName} do definiowania dynamicznych segmentów:
| Wzorzec | Przykładowy URL | Wyodrębnione parametry |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Każdy parametr trasy można powiązać ze schematem dokumentu w celu walidacji:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Powiązanie instruuje CMS, aby wyodrębnił segment lang, zwalidował go względem schematu language, a następnie udostępnił pełny dokument w rozwiązanych parametrach.
Skrypt CEL pobierający lokalizowaną treść:
documents.get("greeting", meta.params.lang).headline
| URL | meta.params.lang | Wynik |
|---|---|---|
/ko/landingPage | "ko" | "Welcome" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "Welcome" |
Dla tras takich jak /{country}/{lang}/products:
// Pobierz nazwę kraju
documents.get("country", meta.params.country).name
// Pobierz listę produktów na podstawie kraju
documents.find("product", { "where": { "country": meta.params.country } })
// Połącz powitanie zależne od kraju i języka użytkownika
documents.get("greeting", meta.params.lang).headline + " from " + documents.get("country", meta.params.country).name
CMS waliduje parametry hierarchicznie: najpierw country względem schematu country, następnie lang względem schematu language, a opcjonalnie sprawdza, czy lang znajduje się w tablicy country.languages[].
meta.segments — dostęp do surowej ścieżki URLmeta.segments udostępnia surową ścieżkę URL jako tablicę. Jest przydatne, gdy potrzebujesz dostępu pozycyjnego bez nazwanych parametrów.
| Ścieżka URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
meta.segments, a kiedy meta.params| Zastosowanie | Najlepsze podejście |
|---|---|
| Nazwane parametry ze wzorca trasy | meta.params.lang |
| Dostęp pozycyjny | meta.segments[0] |
| Pobranie głębokości ścieżki | size(meta.segments) |
| Sprawdzenie, czy ścieżka zawiera segment | "admin" in meta.segments |
meta.segments// Pobierz pierwszy segment, często kod języka
meta.segments[0]
// Sprawdź głębokość ścieżki
size(meta.segments) > 2 ? "deep" : "shallow"
// Sprawdź, czy jesteśmy w sekcji administracyjnej
"admin" in meta.segments ? "admin mode" : "public mode"
// Wartość zapasowa: użyj segmentu, jeśli parametr nie jest powiązany
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
metaObiekt meta zawiera cały kontekst bieżącego żądania:
| Właściwość | Typ | Opis |
|---|---|---|
meta.locale | string | Bieżący kod lokalizacji, np. "en-US", "ko-KR", "ar-SA" |
meta.params | Record<string, string> | Parametry trasy wyodrębnione ze wzorca URL |
meta.segments | string[] | Ścieżka URL podzielona na segmenty |
meta.docId | string | null | UUID bieżącego dokumentu, null dla nowych dokumentów |
meta.title | string | Tytuł bieżącego dokumentu |
meta.localeKod lokalizacji jest zgodny z formatem BCP 47:
// Sprawdź lokalizacje języków RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Pobierz tylko część językową
meta.locale.split("-")[0] // Nieobsługiwane — użyj meta.params.lang
meta.paramsParametry tras są zawsze ciągami znaków. CMS waliduje je względem powiązanych schematów przed wykonaniem wyrażenia:
// Dostęp do nazwanego parametru
meta.params.lang
meta.params.country
meta.params.slug
// Sprawdź, czy parametr istnieje
has(meta.params.category)
// Użyj w pobieraniu dokumentu
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
meta.docId i meta.titlemeta.docId zawiera UUID bieżącego dokumentu i jest przydatne w skryptach odwołujących się do siebie:
meta.docId != null ? "editing" : "creating new"
meta.docId != null ? documents.get("article", meta.docId).status : "draft"
Tytułu dokumentu można użyć na przykład tak:
"Editing: " + meta.title
meta.title.contains("Draft") ? "work in progress" : "published"
documents.ref() — łańcuchowe wyszukiwanieGdy schemat jest znany, ale identyfikator jest dynamiczny, możesz użyć czytelniejszej składni:
// Tradycyjne podejście
documents.get("airports", meta.params.code).name
// Za pomocą ref()
documents.ref("airports").get(meta.params.code).name
Oba sposoby są równoważne, ale ref() wyraźniej pokazuje dynamiczną część odwołania.
documents.get("schema", "identifier") // Pobierz jeden dokument
documents.get("schema", "id").fieldName // Pobierz konkretne pole
documents.find("schema") // Pobierz wszystkie dokumenty
documents.find("schema", { "where": {...}}) // Zapytanie z filtrem
documents.ref("schema").get(identifier) // Łańcuchowe wyszukiwanie
documents.translated("schema", "id", "fr") // Pobierz z określoną lokalizacją
meta.locale // "en-US", "ar-SA" itd.
meta.params.xyz // Parametr URL o nazwie "xyz"
meta.segments // Ścieżka URL jako tablica
meta.segments[0] // Pierwszy segment ścieżki
meta.docId // ID bieżącego dokumentu lub null
meta.title // Tytuł bieżącego dokumentu
doc.fieldName // Wartość pola bieżącego dokumentu
// Porównanie
== != < <= > >=
// Logika
&& || !
// Operator trójargumentowy
condition ? valueIfTrue : valueIfFalse
// Przynależność
"value" in listOrMap
size(list) // Liczba elementów
size(string) // Długość ciągu znaków
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Sprawdź, czy właściwość istnieje
hasProperty(obj, "key") // Sprawdź, czy obiekt ma klucz
Jeśli coś pójdzie nie tak, zobaczysz jeden z poniższych komunikatów:
| Błąd | Znaczenie |
|---|---|
SYNTAX_ERROR | Literówka w skrypcie, brak cudzysłowu lub nieprawidłowy operator |
TYPE_ERROR | Połączono typy, których nie można ze sobą używać |
RUNTIME_ERROR | Skrypt został uruchomiony, ale napotkał problem, np. niezdefiniowaną zmienną |
FETCH_LIMIT_EXCEEDED | Pobieranych jest zbyt wiele dokumentów, maksymalnie 50 |
TIMEOUT | Skrypt działał zbyt długo, maksymalnie 5 sekund |
AST_DEPTH_EXCEEDED | Wyrażenie jest zbyt głęboko zagnieżdżone, maksymalna głębokość to 50 |
SCRIPT_TOO_LONG | Skrypt przekracza limit 5000 znaków |
Silnik CEL został zaprojektowany z myślą o rozszerzalności. Planowane możliwości obejmują:
// W przyszłości: wywoływanie zewnętrznych usług przez MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// W przyszłości: generowanie treści z użyciem 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"])
Możliwości te zostaną dodane za pośrednictwem systemu zarejestrowanych funkcji, z zachowaniem zgodności wstecznej z istniejącymi skryptami.
documents. lub meta., a edytor pokaże dostępne opcje.documents.get("schema", "id"), a dopiero potem dodaj .fieldName.!= null ? ... : ....documents.get() lub documents.find() jest wliczane do limitu 50 pobrań.meta.params zamiast meta.segments — nazwane parametry są walidowane i bardziej niezawodne.has() dla opcjonalnych parametrów — przed odczytem sprawdź has(meta.params.category).documents.ref() dla dynamicznych identyfikatorów — zapewnia czytelniejszą składnię.doc.fieldName dla odwołań do bieżącego dokumentu — umożliwia dostęp do jego pól w wyrażeniach obliczanych.Ta sekcja opisuje zaawansowane wzorce łączenia dokumentów i budowania relacyjnych struktur treści.
Najprostsza forma polega na tym, że jeden dokument odwołuje się do drugiego za pomocą identyfikatora.
// Artykuł przechowuje ID autora; pobierz nazwę autora
documents.get("author", documents.get("article", "intro").authorId).name
documents.ref()// Tradycyjne podejście
documents.get("country", documents.get("airport", meta.params.code).countryCode).name
// Za pomocą ref()
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name
// Lotnisko → Kraj → Region → Kontynent
documents.get("continent",
documents.get("region",
documents.get("country",
documents.get("airport", meta.params.code).countryCode
).regionCode
).continentCode
).name
// Pobierz zlokalizowaną nazwę kraju dla lotniska
documents.translated("country",
documents.get("airport", meta.params.code).countryCode,
meta.params.lang
).name
Ten przykład tworzy wielojęzyczną stronę docelową dostępną pod adresem /{lang}/landingPage.
W panelu administracyjnym CMS utwórz niestandardowy schemat o nazwie greeting.
Utwórz dokument dla każdego języka, na przykład greeting/ko, greeting/en i greeting/ja.
Utwórz stronę z konfiguracją:
/{lang}/landingPagelang → komponent languageDodaj blok hero do trasy i użyj następujących skryptów:
// Nagłówek
documents.get("greeting", meta.params.lang).headline
// Podnagłówek
documents.get("greeting", meta.params.lang).subheadline
// Tekst CTA
documents.get("greeting", meta.params.lang).ctaText
// URL CTA
documents.get("greeting", meta.params.lang).ctaUrl
Dodaj trasę typu catch-all. ParametricRoutePage rozwiązuje stronę, wyodrębnia meta.params z adresu URL, ocenia powiązania CEL po stronie serwera i renderuje każdy blok za pomocą rejestru.
Odwiedź poniższe adresy, aby zobaczyć zlokalizowaną treść:
| URL | Oczekiwany nagłówek |
|---|---|
/ko/landingPage | 환영 |
/en/landingPage | Welcome |
/ja/landingPage | いらっしゃいませ |
Gdy użytkownik odwiedza /ko/landingPage:
/{lang}/landingPage.meta.params.lang = "ko"."ko" istnieje w schemacie language.documents.get("greeting", meta.params.lang) rozwiązują się do treści koreańskiej.CelMeta (TypeScript)interface CelMeta {
/** Bieżący kod lokalizacji */
locale: string;
/** Parametry trasy wyodrębnione z adresu URL */
params: Record<string, string>;
/** Segmenty ścieżki URL */
segments: string[];
/** ID bieżącego dokumentu */
docId: string | null;
/** Tytuł bieżącego dokumentu */
title: string;
}
Funkcja extractParams przetwarza ścieżki URL:
Wzorzec: /{country}/{lang}/products
Ścieżka: /us/en/products
Algorytm:
1. Normalizuje obie wartości (usuwa końcowe ukośniki)
2. Dzieli je na segmenty
3. Porównuje liczbę segmentów
4. Dla każdej pary segmentów wyodrębnia parametr, jeśli wzorzec używa : lub {}
5. Zwraca: { country: "us", lang: "en" }
// Proste powiązanie (używa pola "code")
{ "lang": "language" }
// Szczegółowe powiązanie (niestandardowe pole slug)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
}
Podczas pobierania za pomocą documents.get(schema, identifier) system sprawdza kolejno:
id.content.code.content.slug.title.Dzięki temu można elastycznie odwoływać się do dokumentów za pomocą dowolnego unikatowego identyfikatora.