From 3dd03a13157b1294d92a71ff70218d60c1d30960 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 11:34:06 +0100 Subject: [PATCH 01/14] Enhance documentation guidelines for link definitions and footers across multiple files --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 1 + .cursor/commands/review-docs.md | 1 + .cursor/rules/hugo-content-markdown.mdc | 21 ++++++++++++++++++- .../skills/add-documentation-page/SKILL.md | 4 ++-- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 30f769e76..a819e95bc 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure` paths likely valid; flag new `Cortex.*` keys. +3. **Links/media** — `url`/`section`/`figure` paths likely valid; flag new `Cortex.*` keys. Flag duplicate `{{< url path=… >}}` defs for the same path, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). 4. **Security** — no secrets, config, or ignored paths in the diff. 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 28eedac1e..447c4eb47 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -11,4 +11,5 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Plan multi-page work before editing; prefer `_shared` over duplication. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. - Respect `.cursorignore`; ask for `urls.toml` snippets when adding new `url` paths. +- Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 90e1ec320..175000db8 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -10,5 +10,6 @@ Review current doc changes (branch diff or open files). 3. No edits to ignored paths or unrelated doc versions. 4. Tone matches surrounding customer-facing procedural docs. 5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. +6. Link footers: flag duplicate `{{< url path=… >}}` for the same path, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index 16a35b86f..ee53bd316 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -9,5 +9,24 @@ alwaysApply: false - Frontmatter: `title`, `linkTitle`, `description`, `weight`. - Literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body where siblings do. - Reuse `_shared` + `{{< section "/path.md" >}}` instead of copy-paste. -- Internal links: `{{< url path="Cortex...." >}}` like neighboring pages. - Procedural tone; no drive-by edits across doc versions. + +## Reference-style `url` links + +Internal links use reference definitions with `{{< url path="Cortex...." >}}`, matching neighboring pages. + +- **One definition per `urls.toml` path** on the page. Do not add a second `[OtherText]: {{< url path="same…" >}}` for the same path. +- Prefer a stable canonical label (usually the See Also / page title form, e.g. `items`, `Keys`). +- Implicit `[Label][]` only when `Label` matches the definition label **exactly** (including case). Case mismatches warn in markdown tooling — do not rely on case-insensitive resolution. +- When display text differs in wording **or** case, use `[variant text][canonical]` so the second bracket matches the definition label’s case exactly. + +```markdown + +… called [items][] … each [item][items] … see [Items][items] … + +[items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} + + +[Items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} +[item]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} +``` diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index 100990de4..4f1948765 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -16,7 +16,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec 2. Set `weight` relative to neighbors (lower = earlier in sidebar). 3. Use literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body. 4. For install/guide style, use `# {{% param title %}}` and match callouts (`pageinfo`, `alert`). -5. Add next-step links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages. +5. Add next-step links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages. One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). 6. If content repeats across pages, add `{version}/_shared/...` and `{{< section "/path.md" >}}` instead. 7. Do not edit other version trees unless requested. @@ -24,5 +24,5 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Frontmatter complete - [ ] Branding matches `ctx` convention -- [ ] Links follow section `url` patterns +- [ ] Links follow section `url` patterns; footers dedupe by path with shared labels (exact case) - [ ] Scope limited to requested version From bb89bd6b099f2a8d777ad00c13eb6ec1f2dd351e Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 11:45:24 +0100 Subject: [PATCH 02/14] Enhance documentation on link key management and security protocols across multiple files --- .cursor/agents/docs-reviewer.md | 4 ++-- .cursor/agents/docs-writer.md | 2 +- .cursor/commands/new-doc-page.md | 2 +- .cursor/commands/review-docs.md | 2 +- .cursor/rules/hugo-content-markdown.mdc | 9 +++++++++ .cursor/rules/hugo-shortcodes.mdc | 9 ++++++++- .cursor/rules/security-cursorignore.mdc | 4 ++-- .cursor/skills/add-documentation-page/SKILL.md | 5 +++-- .cursorignore | 3 +++ AGENTS.md | 4 ++-- 10 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index a819e95bc..4fc66f63b 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,8 +11,8 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure` paths likely valid; flag new `Cortex.*` keys. Flag duplicate `{{< url path=… >}}` defs for the same path, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). -4. **Security** — no secrets, config, or ignored paths in the diff. +3. **Links/media** — `url`/`section`/`figure` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). Flag duplicate `{{< url path=… >}}` defs for the same path, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). +4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. Provide prioritized findings: must-fix vs nice-to-have. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 447c4eb47..86fdf1a08 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -10,6 +10,6 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Follow [AGENTS.md](../../AGENTS.md) and [content/AGENTS.md](../../content/AGENTS.md). - Plan multi-page work before editing; prefer `_shared` over duplication. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. -- Respect `.cursorignore`; ask for `urls.toml` snippets when adding new `url` paths. +- Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first; edit `urls.toml` only when adding a truly new page key; never add a second key that maps to the same page URL. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/new-doc-page.md b/.cursor/commands/new-doc-page.md index 072c1899d..7816a4b02 100644 --- a/.cursor/commands/new-doc-page.md +++ b/.cursor/commands/new-doc-page.md @@ -8,5 +8,5 @@ Create a new documentation page for CORTEX Product Portal. 1. Confirm doc version (e.g. 2026.3) and target folder under `content/en/docs/{version}/`. 2. Open the closest sibling `_index.md` or leaf `.md` and mirror structure. 3. Apply the add-documentation-page skill workflow. -4. List files created/changed and any `urls.toml` keys the user must add (do not read ignored data files). +4. Look up keys in `data/urls.toml` (and sibling pages) before adding any. List files created/changed and any **new** `urls.toml` keys added (at most one per new page; never a second key for an existing page URL). 5. Summarize nav impact (`weight`, parent section). diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 175000db8..8eb3a6aab 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -9,7 +9,7 @@ Review current doc changes (branch diff or open files). 2. Shortcodes valid (`section` paths exist under `_shared`, `figure` paths under `content/static`). 3. No edits to ignored paths or unrelated doc versions. 4. Tone matches surrounding customer-facing procedural docs. -5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. +5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL. 6. Link footers: flag duplicate `{{< url path=… >}}` for the same path, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index ee53bd316..55d623e04 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -15,6 +15,15 @@ alwaysApply: false Internal links use reference definitions with `{{< url path="Cortex...." >}}`, matching neighboring pages. +### Choosing a `Cortex.*` key + +1. **Reuse before invent** — Grep content for an existing `{{< url path="Cortex...." >}}` to the target page; copy that key **exactly**. +2. **Look up before add** — Search [`data/urls.toml`](../../data/urls.toml) for any key whose URL already targets that page (same path, with or without trailing slash / `_index`). +3. **Never “normalize”** — Do not invent a parallel key that mirrors the folder tree when an atypical key already points at the same page (e.g. historical suffixes like `New`). +4. **New pages only** — Add at most one new `urls.toml` entry per new page, matching sibling key style in that section; confirm no existing value already maps there. + +### Page footers + - **One definition per `urls.toml` path** on the page. Do not add a second `[OtherText]: {{< url path="same…" >}}` for the same path. - Prefer a stable canonical label (usually the See Also / page title form, e.g. `items`, `Keys`). - Implicit `[Label][]` only when `Label` matches the definition label **exactly** (including case). Case mismatches warn in markdown tooling — do not rely on case-insensitive resolution. diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index 58091abd2..f2d6f8646 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -13,8 +13,15 @@ Read `layouts/shortcodes/*.html` before changing usage. | `ctx` | Renders CORTEX; not in `title`/`linkTitle` | | `version` | Uses `{version}/_shared/currentVersion.md` | | `section` | Path under `{version}/_shared/`; must exist | -| `url` | Keys from `urls.toml` (ignored — ask user for keys) | +| `url` | Keys from `data/urls.toml` — look up existing keys first; never add a second key for the same page URL | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | +### `url` key workflow + +1. Grep content for an existing `{{< url path="Cortex...." >}}` to the target; copy that key exactly. +2. Search `data/urls.toml` for any key whose URL already targets that page (same path ± trailing slash / `_index`). +3. Do not invent a “normalized” key from the folder tree when an atypical key already maps there. +4. For brand-new pages only: add at most one new entry, matching sibling key style in that section. + Template edits affect all versions — scope testing accordingly. diff --git a/.cursor/rules/security-cursorignore.mdc b/.cursor/rules/security-cursorignore.mdc index 20bbdd05a..a19d24a6b 100644 --- a/.cursor/rules/security-cursorignore.mdc +++ b/.cursor/rules/security-cursorignore.mdc @@ -7,6 +7,6 @@ alwaysApply: true Follow [.cursorignore](../../.cursorignore). -Do not read/commit: `config.*`, `data/**`, `urls.toml`, CI configs, `.env*`, certs/keys, `public/**`, `node_modules/**`, internal/draft content paths. +Do not read/commit: `config.*`, other `data/**` (except `data/urls.toml`), CI configs, `.env*`, certs/keys, `public/**`, `node_modules/**`, internal/draft content paths. -If link keys are needed, copy from an existing page in the same section or ask the user. +**Exception:** `data/urls.toml` may be read and updated for link keys. Do not read other ignored `data/**` or `*.toml` files. Look up existing keys in `data/urls.toml` before adding any new `Cortex.*` path — never invent a parallel key for a page that already has one. diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index 4f1948765..ddb7b440b 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -16,7 +16,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec 2. Set `weight` relative to neighbors (lower = earlier in sidebar). 3. Use literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body. 4. For install/guide style, use `# {{% param title %}}` and match callouts (`pageinfo`, `alert`). -5. Add next-step links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages. One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). +5. Add next-step links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key. Never add a second `urls.toml` entry for a page that already has one. For a brand-new page, add at most one new key (sibling style). One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). 6. If content repeats across pages, add `{version}/_shared/...` and `{{< section "/path.md" >}}` instead. 7. Do not edit other version trees unless requested. @@ -24,5 +24,6 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Frontmatter complete - [ ] Branding matches `ctx` convention -- [ ] Links follow section `url` patterns; footers dedupe by path with shared labels (exact case) +- [ ] Links: keys reused or looked up in `data/urls.toml`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) +- [ ] At most one new `urls.toml` key if this page is new - [ ] Scope limited to requested version diff --git a/.cursorignore b/.cursorignore index a580df2d1..4bf09ffc2 100644 --- a/.cursorignore +++ b/.cursorignore @@ -43,6 +43,9 @@ data/** *.toml *.csv +# Allow link-key lookup / updates (keep other data/*.toml ignored) +!data/urls.toml + # Docsy-specific # Includes navigation and structure that may leak internal structure content/en/docs/internal/** diff --git a/AGENTS.md b/AGENTS.md index 554300f0b..b7f1fcf24 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ For multi-page or cross-version changes, outline affected paths first (which ver ### Security and ignored paths -Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, `data/**`, CI workflows, secrets, or internal/draft paths. If `urls.toml` is needed, ask the user for the relevant key or snippet. +Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other `data/**`, CI workflows, secrets, or internal/draft paths. **Exception:** read and update [`data/urls.toml`](data/urls.toml) for link keys — look up existing keys first; do not add a second key that maps to the same page. ## Project agent assets @@ -54,6 +54,6 @@ Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, `data ## What not to do -- Do not modify `public/`, `node_modules/`, or ignored config/data files. +- Do not modify `public/`, `node_modules/`, or ignored config/data files (except allowed updates to `data/urls.toml`). - Do not invent install commands or CI steps not documented in-repo. - Do not remove or weaken `.cursorignore` without explicit user approval. From c03bee95096ec9ac5e32e877c3633179fa5d1cbe Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 11:58:37 +0100 Subject: [PATCH 03/14] Enhance documentation guidelines for same-page and cross-page link definitions across multiple files --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 1 + .cursor/commands/review-docs.md | 3 ++- .cursor/rules/hugo-content-markdown.mdc | 24 +++++++++++++++++++ .cursor/rules/hugo-shortcodes.mdc | 1 + .../skills/add-documentation-page/SKILL.md | 5 ++-- 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 4fc66f63b..02f7c9cab 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). Flag duplicate `{{< url path=… >}}` defs for the same path, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). +3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 86fdf1a08..9e391d95a 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -12,4 +12,5 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. - Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first; edit `urls.toml` only when adding a truly new page key; never add a second key that maps to the same page URL. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). +- Same-page `ref` defs: one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 8eb3a6aab..75b651ff7 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -10,6 +10,7 @@ Review current doc changes (branch diff or open files). 3. No edits to ignored paths or unrelated doc versions. 4. Tone matches surrounding customer-facing procedural docs. 5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL. -6. Link footers: flag duplicate `{{< url path=… >}}` for the same path, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). +6. Link footers (`url` and `ref`): flag duplicate defs for the same target, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). +7. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index 55d623e04..caf1d3b02 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -39,3 +39,27 @@ Internal links use reference definitions with `{{< url path="Cortex...." >}}`, m [Items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} [item]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} ``` + +## Same-page `ref` links + +Links to headings or fragments **on the current page** use reference definitions with Hugo’s built-in `{{< ref "#slug" >}}`, then the same in-body pattern as `url` links. Pattern: `content/en/docs/2026.3/Reference/Concepts/working-with/concurrency/semaphores/what-is-a-semaphore.md`. + +- **Never** write inline `[text](#slug)`. Always add a footer definition and reference it. +- **One definition per anchor** on the page. Do not add a second `[OtherText]: {{< ref "#same-slug" >}}` for the same fragment. +- Prefer a stable canonical label (usually the heading text form, e.g. `Queuing Semaphore`). +- Implicit `[Label][]` only when `Label` matches the definition label **exactly** (including case). Case mismatches warn in markdown tooling — do not rely on case-insensitive resolution. +- When display text differs in wording **or** case, use `[variant text][canonical]` so the second bracket matches the definition label’s case exactly. + +```markdown + +… [queue][Queuing Semaphore] or [not queue][Non-Queuing Semaphore] … +… see [Queuing Semaphore][] … + +[Queuing Semaphore]: {{< ref "#queuing-semaphore" >}} +[Non-Queuing Semaphore]: {{< ref "#non-queuing-semaphore" >}} + + +… [Queuing Semaphore](#queuing-semaphore) … +[queuing semaphore]: {{< ref "#queuing-semaphore" >}} +[Queuing Semaphore]: {{< ref "#queuing-semaphore" >}} +``` diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index f2d6f8646..ea87caf2f 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -14,6 +14,7 @@ Read `layouts/shortcodes/*.html` before changing usage. | `version` | Uses `{version}/_shared/currentVersion.md` | | `section` | Path under `{version}/_shared/`; must exist | | `url` | Keys from `data/urls.toml` — look up existing keys first; never add a second key for the same page URL | +| `ref` | Hugo built-in (not under `layouts/shortcodes/`). Same-page headings: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. Also relative `.md` / `.md#fragment` when neighboring pages already use that pattern | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index ddb7b440b..593e35d3a 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -16,7 +16,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec 2. Set `weight` relative to neighbors (lower = earlier in sidebar). 3. Use literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body. 4. For install/guide style, use `# {{% param title %}}` and match callouts (`pageinfo`, `alert`). -5. Add next-step links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key. Never add a second `urls.toml` entry for a page that already has one. For a brand-new page, add at most one new key (sibling style). One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). +5. Add next-step / cross-page links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key. Never add a second `urls.toml` entry for a page that already has one. For a brand-new page, add at most one new key (sibling style). One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). Same-page headings: one `[Label]: {{< ref "#slug" >}}` per anchor; same exact-case / `[variant][canonical]` rules as `url` — never inline `[text](#slug)`. 6. If content repeats across pages, add `{version}/_shared/...` and `{{< section "/path.md" >}}` instead. 7. Do not edit other version trees unless requested. @@ -24,6 +24,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Frontmatter complete - [ ] Branding matches `ctx` convention -- [ ] Links: keys reused or looked up in `data/urls.toml`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) +- [ ] Cross-page links: keys reused or looked up in `data/urls.toml`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) +- [ ] Same-page links: `ref` footers (`#slug`), not inline `[…](#…)`; one def per anchor; exact-case labels / `[variant][canonical]` - [ ] At most one new `urls.toml` key if this page is new - [ ] Scope limited to requested version From 53fafa98b803061b71e3581939e15f3331ce15f6 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 12:07:42 +0100 Subject: [PATCH 04/14] Enhance documentation guidelines for same-page links and reference definitions across multiple files --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 2 +- .cursor/commands/review-docs.md | 2 +- .cursor/rules/hugo-content-markdown.mdc | 4 +++- .cursor/rules/hugo-shortcodes.mdc | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 02f7c9cab..a5747e90b 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. +3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 9e391d95a..c4c56a18f 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -12,5 +12,5 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. - Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first; edit `urls.toml` only when adding a truly new page key; never add a second key that maps to the same page URL. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). -- Same-page `ref` defs: one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. +- Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + `urls.toml`); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 75b651ff7..72c7fd3ea 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -11,6 +11,6 @@ Review current doc changes (branch diff or open files). 4. Tone matches surrounding customer-facing procedural docs. 5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL. 6. Link footers (`url` and `ref`): flag duplicate defs for the same target, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). -7. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. +7. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index caf1d3b02..10ffe0f69 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -44,6 +44,7 @@ Internal links use reference definitions with `{{< url path="Cortex...." >}}`, m Links to headings or fragments **on the current page** use reference definitions with Hugo’s built-in `{{< ref "#slug" >}}`, then the same in-body pattern as `url` links. Pattern: `content/en/docs/2026.3/Reference/Concepts/working-with/concurrency/semaphores/what-is-a-semaphore.md`. +- **`ref` is `#slug` only.** Do not use `ref` with relative paths (`other-page.md`, `../foo.md#bar`, or `page.md#fragment`). Cross-page targets (including fragments on other pages) use `{{< url path="Cortex...." >}}` — look up or add a key in `data/urls.toml` per the `url` workflow above. - **Never** write inline `[text](#slug)`. Always add a footer definition and reference it. - **One definition per anchor** on the page. Do not add a second `[OtherText]: {{< ref "#same-slug" >}}` for the same fragment. - Prefer a stable canonical label (usually the heading text form, e.g. `Queuing Semaphore`). @@ -58,8 +59,9 @@ Links to headings or fragments **on the current page** use reference definitions [Queuing Semaphore]: {{< ref "#queuing-semaphore" >}} [Non-Queuing Semaphore]: {{< ref "#non-queuing-semaphore" >}} - + … [Queuing Semaphore](#queuing-semaphore) … [queuing semaphore]: {{< ref "#queuing-semaphore" >}} [Queuing Semaphore]: {{< ref "#queuing-semaphore" >}} +[value type]: {{< ref "what-is-a-data-type.md#value-types" >}} ``` diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index ea87caf2f..8793d436b 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -14,7 +14,7 @@ Read `layouts/shortcodes/*.html` before changing usage. | `version` | Uses `{version}/_shared/currentVersion.md` | | `section` | Path under `{version}/_shared/`; must exist | | `url` | Keys from `data/urls.toml` — look up existing keys first; never add a second key for the same page URL | -| `ref` | Hugo built-in (not under `layouts/shortcodes/`). Same-page headings: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. Also relative `.md` / `.md#fragment` when neighboring pages already use that pattern | +| `ref` | Hugo built-in (not under `layouts/shortcodes/`). **Same-page `#slug` only**: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. **Never** relative paths (`page.md`, `../foo.md#bar`) — use `url` + `urls.toml` instead | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | From 2d3b89da7c1d8e15bb5164d4229893b41afddb91 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 13:13:49 +0100 Subject: [PATCH 05/14] Enhance documentation guidelines for link definitions, emphasizing the use of reference-style links and the avoidance of plain markdown external links across multiple files. --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 2 +- .cursor/commands/new-doc-page.md | 2 +- .cursor/commands/review-docs.md | 3 ++- .cursor/rules/hugo-content-markdown.mdc | 22 ++++++++++++------- .cursor/rules/hugo-shortcodes.mdc | 10 ++++----- .cursor/rules/security-cursorignore.mdc | 2 +- .../skills/add-documentation-page/SKILL.md | 6 ++--- AGENTS.md | 4 ++-- 9 files changed, 30 insertions(+), 23 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index a5747e90b..3b5fca0f1 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new `Cortex.*` keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page as an atypical key). For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. +3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page or external URL as an atypical key). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index c4c56a18f..52fef647b 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -10,7 +10,7 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Follow [AGENTS.md](../../AGENTS.md) and [content/AGENTS.md](../../content/AGENTS.md). - Plan multi-page work before editing; prefer `_shared` over duplication. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. -- Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first; edit `urls.toml` only when adding a truly new page key; never add a second key that maps to the same page URL. +- Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first (internal `Cortex.*` and external namespaces such as `MSDocs.…`, `Postman.…`); edit `urls.toml` only when adding a truly new destination key; never add a second key that maps to the same URL. Never write plain markdown external links (`[text](https://…)`) or bare `https://` URLs — use `{{< url path="…" >}}` like any other link. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). - Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + `urls.toml`); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/new-doc-page.md b/.cursor/commands/new-doc-page.md index 7816a4b02..8ba81ae47 100644 --- a/.cursor/commands/new-doc-page.md +++ b/.cursor/commands/new-doc-page.md @@ -8,5 +8,5 @@ Create a new documentation page for CORTEX Product Portal. 1. Confirm doc version (e.g. 2026.3) and target folder under `content/en/docs/{version}/`. 2. Open the closest sibling `_index.md` or leaf `.md` and mirror structure. 3. Apply the add-documentation-page skill workflow. -4. Look up keys in `data/urls.toml` (and sibling pages) before adding any. List files created/changed and any **new** `urls.toml` keys added (at most one per new page; never a second key for an existing page URL). +4. Look up keys in `data/urls.toml` (and sibling pages) before adding any — for this page’s `Cortex.*` key and any external destinations. Never use plain `[text](https://…)` links. List files created/changed and any **new** `urls.toml` keys added (at most one per new page; never a second key for an existing page or external URL). 5. Summarize nav impact (`weight`, parent section). diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 72c7fd3ea..113066ada 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -9,8 +9,9 @@ Review current doc changes (branch diff or open files). 2. Shortcodes valid (`section` paths exist under `_shared`, `figure` paths under `content/static`). 3. No edits to ignored paths or unrelated doc versions. 4. Tone matches surrounding customer-facing procedural docs. -5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL. +5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL (internal or external). 6. Link footers (`url` and `ref`): flag duplicate defs for the same target, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). 7. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. +8. External links: flag plain markdown `[…](https://…)` / `[…](http://…)` and bare URLs; expect `{{< url path="…" >}}` with a key looked up (or added) in `data/urls.toml`. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index 10ffe0f69..659a049c4 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -13,14 +13,16 @@ alwaysApply: false ## Reference-style `url` links -Internal links use reference definitions with `{{< url path="Cortex...." >}}`, matching neighboring pages. +Every navigable link—docs pages **and** third-party / external sites—uses a reference definition with `{{< url path="…" >}}`. Do **not** use plain markdown external links (`[text](https://…)`) or bare `https://…` / `http://…` URLs in body text. -### Choosing a `Cortex.*` key +Keys live in [`data/urls.toml`](../../data/urls.toml): internal docs under `Cortex.*`, external destinations under other top-level namespaces (e.g. `MSDocs.…`, `GitHub.…`, `Postman.…`, `Wikipedia.…`, `W3.…`). -1. **Reuse before invent** — Grep content for an existing `{{< url path="Cortex...." >}}` to the target page; copy that key **exactly**. -2. **Look up before add** — Search [`data/urls.toml`](../../data/urls.toml) for any key whose URL already targets that page (same path, with or without trailing slash / `_index`). -3. **Never “normalize”** — Do not invent a parallel key that mirrors the folder tree when an atypical key already points at the same page (e.g. historical suffixes like `New`). -4. **New pages only** — Add at most one new `urls.toml` entry per new page, matching sibling key style in that section; confirm no existing value already maps there. +### Choosing a `urls.toml` key + +1. **Reuse before invent** — Grep content for an existing `{{< url path="…" >}}` to the target; copy that key **exactly**. +2. **Look up before add** — Search [`data/urls.toml`](../../data/urls.toml) for any key whose URL already targets that destination (same path ± trailing slash / `_index` for internal pages; exact `https://` / `http://` URL for external sites). +3. **Never “normalize”** — Do not invent a parallel key that mirrors the folder tree (or a new external namespace) when an atypical key already points at the same URL (e.g. historical suffixes like `New`). +4. **New destinations only** — Add a new `urls.toml` entry only when none maps there: at most one per new docs page (`Cortex.*`, sibling key style); for external URLs, match sibling namespace style in that section (`MSDocs.…`, `Postman.…`, etc.). Confirm no existing value already maps to that URL. ### Page footers @@ -32,10 +34,14 @@ Internal links use reference definitions with `{{< url path="Cortex...." >}}`, m ```markdown … called [items][] … each [item][items] … see [Items][items] … +… download [Postman][] … [items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} +[Postman]: {{< url path="Postman.Downloads.MainDoc" >}} - + +… [Postman](https://www.postman.com/downloads/) … +… https://learn.microsoft.com/en-us/dotnet/ … [Items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} [item]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} ``` @@ -44,7 +50,7 @@ Internal links use reference definitions with `{{< url path="Cortex...." >}}`, m Links to headings or fragments **on the current page** use reference definitions with Hugo’s built-in `{{< ref "#slug" >}}`, then the same in-body pattern as `url` links. Pattern: `content/en/docs/2026.3/Reference/Concepts/working-with/concurrency/semaphores/what-is-a-semaphore.md`. -- **`ref` is `#slug` only.** Do not use `ref` with relative paths (`other-page.md`, `../foo.md#bar`, or `page.md#fragment`). Cross-page targets (including fragments on other pages) use `{{< url path="Cortex...." >}}` — look up or add a key in `data/urls.toml` per the `url` workflow above. +- **`ref` is `#slug` only.** Do not use `ref` with relative paths (`other-page.md`, `../foo.md#bar`, or `page.md#fragment`). Cross-page targets (including fragments on other pages) and external sites use `{{< url path="…" >}}` — look up or add a key in `data/urls.toml` per the `url` workflow above. - **Never** write inline `[text](#slug)`. Always add a footer definition and reference it. - **One definition per anchor** on the page. Do not add a second `[OtherText]: {{< ref "#same-slug" >}}` for the same fragment. - Prefer a stable canonical label (usually the heading text form, e.g. `Queuing Semaphore`). diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index 8793d436b..44c526428 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -13,16 +13,16 @@ Read `layouts/shortcodes/*.html` before changing usage. | `ctx` | Renders CORTEX; not in `title`/`linkTitle` | | `version` | Uses `{version}/_shared/currentVersion.md` | | `section` | Path under `{version}/_shared/`; must exist | -| `url` | Keys from `data/urls.toml` — look up existing keys first; never add a second key for the same page URL | +| `url` | Keys from `data/urls.toml` (internal `Cortex.*` and external namespaces). Look up existing keys first; never add a second key for the same target URL. Never plain `[text](https://…)` or bare URLs | | `ref` | Hugo built-in (not under `layouts/shortcodes/`). **Same-page `#slug` only**: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. **Never** relative paths (`page.md`, `../foo.md#bar`) — use `url` + `urls.toml` instead | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | ### `url` key workflow -1. Grep content for an existing `{{< url path="Cortex...." >}}` to the target; copy that key exactly. -2. Search `data/urls.toml` for any key whose URL already targets that page (same path ± trailing slash / `_index`). -3. Do not invent a “normalized” key from the folder tree when an atypical key already maps there. -4. For brand-new pages only: add at most one new entry, matching sibling key style in that section. +1. Grep content for an existing `{{< url path="…" >}}` to the target; copy that key exactly. +2. Search `data/urls.toml` for any key whose URL already targets that destination (same path ± trailing slash / `_index` for docs pages; exact `https://` / `http://` for external sites). +3. Do not invent a “normalized” key from the folder tree (or a new external namespace) when an atypical key already maps there. +4. Add a new entry only when none exists: brand-new docs pages get at most one `Cortex.*` key (sibling style); external URLs use matching sibling namespace style (`MSDocs.…`, `GitHub.…`, `Postman.…`, etc.). Template edits affect all versions — scope testing accordingly. diff --git a/.cursor/rules/security-cursorignore.mdc b/.cursor/rules/security-cursorignore.mdc index a19d24a6b..d420ef7e0 100644 --- a/.cursor/rules/security-cursorignore.mdc +++ b/.cursor/rules/security-cursorignore.mdc @@ -9,4 +9,4 @@ Follow [.cursorignore](../../.cursorignore). Do not read/commit: `config.*`, other `data/**` (except `data/urls.toml`), CI configs, `.env*`, certs/keys, `public/**`, `node_modules/**`, internal/draft content paths. -**Exception:** `data/urls.toml` may be read and updated for link keys. Do not read other ignored `data/**` or `*.toml` files. Look up existing keys in `data/urls.toml` before adding any new `Cortex.*` path — never invent a parallel key for a page that already has one. +**Exception:** `data/urls.toml` may be read and updated for link keys. Do not read other ignored `data/**` or `*.toml` files. Look up existing keys in `data/urls.toml` before adding any new key (internal `Cortex.*` or external namespaces) — never invent a parallel key for a destination URL that already has one. diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index 593e35d3a..42c79e896 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -16,7 +16,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec 2. Set `weight` relative to neighbors (lower = earlier in sidebar). 3. Use literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body. 4. For install/guide style, use `# {{% param title %}}` and match callouts (`pageinfo`, `alert`). -5. Add next-step / cross-page links with `{{< url path="Cortex...." >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key. Never add a second `urls.toml` entry for a page that already has one. For a brand-new page, add at most one new key (sibling style). One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). Same-page headings: one `[Label]: {{< ref "#slug" >}}` per anchor; same exact-case / `[variant][canonical]` rules as `url` — never inline `[text](#slug)`. +5. Add next-step / cross-page / external links with `{{< url path="…" >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key (internal `Cortex.*` or external namespaces such as `MSDocs.…`, `Postman.…`). Never write plain markdown external links (`[text](https://…)`) or bare URLs. Never add a second `urls.toml` entry for a destination that already has one. For a brand-new page, add at most one new `Cortex.*` key (sibling style); add external keys only when the destination URL is new. One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). Same-page headings: one `[Label]: {{< ref "#slug" >}}` per anchor; same exact-case / `[variant][canonical]` rules as `url` — never inline `[text](#slug)`. 6. If content repeats across pages, add `{version}/_shared/...` and `{{< section "/path.md" >}}` instead. 7. Do not edit other version trees unless requested. @@ -24,7 +24,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Frontmatter complete - [ ] Branding matches `ctx` convention -- [ ] Cross-page links: keys reused or looked up in `data/urls.toml`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) +- [ ] Cross-page and external links: keys reused or looked up in `data/urls.toml`; no plain `[…](https://…)`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) - [ ] Same-page links: `ref` footers (`#slug`), not inline `[…](#…)`; one def per anchor; exact-case labels / `[variant][canonical]` -- [ ] At most one new `urls.toml` key if this page is new +- [ ] At most one new `urls.toml` key if this page is new; external keys only when the URL is new - [ ] Scope limited to requested version diff --git a/AGENTS.md b/AGENTS.md index b7f1fcf24..d45aab891 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ For multi-page or cross-version changes, outline affected paths first (which ver ### Security and ignored paths -Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other `data/**`, CI workflows, secrets, or internal/draft paths. **Exception:** read and update [`data/urls.toml`](data/urls.toml) for link keys — look up existing keys first; do not add a second key that maps to the same page. +Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other `data/**`, CI workflows, secrets, or internal/draft paths. **Exception:** read and update [`data/urls.toml`](data/urls.toml) for link keys — look up existing keys first; do not add a second key that maps to the same destination (docs page or external URL). ## Project agent assets @@ -49,7 +49,7 @@ Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other ## Quality bar - Procedural, customer-facing tone; preserve frontmatter (`title`, `linkTitle`, `description`, `weight`). -- Prefer shortcodes over raw HTML; match `{{< url path="Cortex...." >}}` patterns in the same section. +- Prefer shortcodes over raw HTML; match `{{< url path="…" >}}` for internal **and** external links (never plain `[text](https://…)` or bare URLs). - Scope changes to the version(s) the user requested. ## What not to do From 81b31d2b59b4484b6222a6cee7bbe03c983d1b0a Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 13:23:49 +0100 Subject: [PATCH 06/14] Enhance documentation guidelines for link management, emphasizing heading keys, fragment handling, and avoiding duplicate URLs across multiple files. --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 5 +-- .cursor/commands/new-doc-page.md | 2 +- .cursor/commands/review-docs.md | 7 ++-- .cursor/rules/hugo-content-markdown.mdc | 36 ++++++++++++++----- .cursor/rules/hugo-shortcodes.mdc | 9 ++--- .cursor/rules/security-cursorignore.mdc | 2 +- .../skills/add-documentation-page/SKILL.md | 6 ++-- AGENTS.md | 3 +- 9 files changed, 48 insertions(+), 24 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 3b5fca0f1..566c1bcca 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page or external URL as an atypical key). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. +3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page, heading, or external URL as an atypical key). Prefer heading keys when link text/context names a section — flag page-level keys used for section topics, and flag any `{{< url path="…" >}}#…` footer (expect a dedicated `urls.toml` key with the fragment in the value). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 52fef647b..16483400a 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -10,7 +10,8 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Follow [AGENTS.md](../../AGENTS.md) and [content/AGENTS.md](../../content/AGENTS.md). - Plan multi-page work before editing; prefer `_shared` over duplication. - Use project shortcodes; never hardcode branding where `{{% ctx %}}` is standard. -- Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first (internal `Cortex.*` and external namespaces such as `MSDocs.…`, `Postman.…`); edit `urls.toml` only when adding a truly new destination key; never add a second key that maps to the same URL. Never write plain markdown external links (`[text](https://…)`) or bare `https://` URLs — use `{{< url path="…" >}}` like any other link. +- Respect `.cursorignore`. For `url` keys: look up [`data/urls.toml`](../../data/urls.toml) and sibling pages first (internal `Cortex.*` and external namespaces such as `MSDocs.…`, `Postman.…`); edit `urls.toml` only when adding a truly new destination key; never add a second key that maps to the same full URL (path ± `#fragment` — page and heading are distinct). Never write plain markdown external links (`[text](https://…)`) or bare `https://` URLs — use `{{< url path="…" >}}` like any other link. +- Prefer heading destinations: when prose targets a section on another page, use (or add) a `urls.toml` key whose value includes `#slug`. Never append `#…` after the `url` shortcode. Use a page-level key only for whole-page references. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). -- Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + `urls.toml`); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. +- Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + a `urls.toml` key with any fragment in the value); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/new-doc-page.md b/.cursor/commands/new-doc-page.md index 8ba81ae47..14fd13027 100644 --- a/.cursor/commands/new-doc-page.md +++ b/.cursor/commands/new-doc-page.md @@ -8,5 +8,5 @@ Create a new documentation page for CORTEX Product Portal. 1. Confirm doc version (e.g. 2026.3) and target folder under `content/en/docs/{version}/`. 2. Open the closest sibling `_index.md` or leaf `.md` and mirror structure. 3. Apply the add-documentation-page skill workflow. -4. Look up keys in `data/urls.toml` (and sibling pages) before adding any — for this page’s `Cortex.*` key and any external destinations. Never use plain `[text](https://…)` links. List files created/changed and any **new** `urls.toml` keys added (at most one per new page; never a second key for an existing page or external URL). +4. Look up keys in `data/urls.toml` (and sibling pages) before adding any — for this page’s `Cortex.*` key, any heading destinations (`#slug` in the `urls.toml` value), and any external destinations. Never use plain `[text](https://…)` links. Never append `#…` after `{{< url … >}}`. List files created/changed and any **new** `urls.toml` keys added (page-level plus fragment keys as needed; never a second key for the same full destination URL). 5. Summarize nav impact (`weight`, parent section). diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 113066ada..bcae3b886 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -9,9 +9,10 @@ Review current doc changes (branch diff or open files). 2. Shortcodes valid (`section` paths exist under `_shared`, `figure` paths under `content/static`). 3. No edits to ignored paths or unrelated doc versions. 4. Tone matches surrounding customer-facing procedural docs. -5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL (internal or external). +5. List risks: missing `urls.toml` keys, cross-version drift, broken shared paths. Check `data/urls.toml` for new keys that duplicate an existing target URL (internal or external; full URL including any `#fragment`). 6. Link footers (`url` and `ref`): flag duplicate defs for the same target, and reference labels that do not match the definition label’s case (prefer one canonical def + `[Text][canonical]`). -7. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. -8. External links: flag plain markdown `[…](https://…)` / `[…](http://…)` and bare URLs; expect `{{< url path="…" >}}` with a key looked up (or added) in `data/urls.toml`. +7. Heading links: when link text/context names a section, expect a `urls.toml` key whose value includes `#slug` — flag page-level keys used for section topics, and flag `{{< url path="…" >}}#…` footers (fragment belongs in `urls.toml`, not after the shortcode). +8. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. +9. External links: flag plain markdown `[…](https://…)` / `[…](http://…)` and bare URLs; expect `{{< url path="…" >}}` with a key looked up (or added) in `data/urls.toml`. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index 659a049c4..487961ccf 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -17,40 +17,60 @@ Every navigable link—docs pages **and** third-party / external sites—uses a Keys live in [`data/urls.toml`](../../data/urls.toml): internal docs under `Cortex.*`, external destinations under other top-level namespaces (e.g. `MSDocs.…`, `GitHub.…`, `Postman.…`, `Wikipedia.…`, `W3.…`). +### Prefer heading keys + +When link text or context targets a **specific section/topic** on another page, link to that heading—not the page top: + +1. Confirm the target heading and its Hugo anchor slug on the destination page. +2. Look up [`data/urls.toml`](../../data/urls.toml) for a key whose URL is that page path **including** `#slug`. +3. If none exists, add a sibling-style key under that page’s namespace with the fragment in the value (e.g. `ViewClusterHealth`, `AntivirusExclusions`). Page-without-fragment and `page#slug` are **different** destinations — a new fragment key is allowed and expected. +4. Footer: `[Label]: {{< url path="Cortex.…HeadingKey" >}}` only. **Never** append `#…` after the shortcode (`}}#slug` is forbidden). +5. Use a page-level key (`MainDoc` or equivalent, no fragment) only when the reference is to the page as a whole. + ### Choosing a `urls.toml` key 1. **Reuse before invent** — Grep content for an existing `{{< url path="…" >}}` to the target; copy that key **exactly**. -2. **Look up before add** — Search [`data/urls.toml`](../../data/urls.toml) for any key whose URL already targets that destination (same path ± trailing slash / `_index` for internal pages; exact `https://` / `http://` URL for external sites). +2. **Look up before add** — Search [`data/urls.toml`](../../data/urls.toml) for any key whose URL already targets that destination (same path ± trailing slash / `_index` for internal pages, **including exact `#fragment` when present**; exact `https://` / `http://` URL for external sites). 3. **Never “normalize”** — Do not invent a parallel key that mirrors the folder tree (or a new external namespace) when an atypical key already points at the same URL (e.g. historical suffixes like `New`). -4. **New destinations only** — Add a new `urls.toml` entry only when none maps there: at most one per new docs page (`Cortex.*`, sibling key style); for external URLs, match sibling namespace style in that section (`MSDocs.…`, `Postman.…`, etc.). Confirm no existing value already maps to that URL. +4. **New destinations only** — Add a new `urls.toml` entry only when none maps there: new docs pages get a page-level `Cortex.*` key (sibling style); heading destinations get additional fragment keys as needed; for external URLs, match sibling namespace style in that section (`MSDocs.…`, `Postman.…`, etc.). Confirm no existing value already maps to that full URL (path ± fragment). ### Page footers - **One definition per `urls.toml` path** on the page. Do not add a second `[OtherText]: {{< url path="same…" >}}` for the same path. -- Prefer a stable canonical label (usually the See Also / page title form, e.g. `items`, `Keys`). +- **Never** write `[Label]: {{< url path="…" >}}#slug` — put the fragment in `urls.toml` and use a normal `url` shortcode. +- Prefer a stable canonical label (usually the See Also / page title / heading form, e.g. `items`, `Keys`, `Antivirus exclusions`). - Implicit `[Label][]` only when `Label` matches the definition label **exactly** (including case). Case mismatches warn in markdown tooling — do not rely on case-insensitive resolution. - When display text differs in wording **or** case, use `[variant text][canonical]` so the second bracket matches the definition label’s case exactly. ```markdown - -… called [items][] … each [item][items] … see [Items][items] … + +… see [Antivirus exclusions][] … +… see [Prerequisites][] … … download [Postman][] … -[items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} +[Antivirus exclusions]: {{< url path="Cortex.GettingStarted.OnPremise.InstallInnovationOnly.SingleServerWithoutHA.AntivirusExclusions" >}} +[Prerequisites]: {{< url path="Cortex.GettingStarted.OnPremise.InstallInnovationOnly.SingleServerWithoutHA.Prerequisites" >}} [Postman]: {{< url path="Postman.Downloads.MainDoc" >}} - + … [Postman](https://www.postman.com/downloads/) … … https://learn.microsoft.com/en-us/dotnet/ … +[Antivirus exclusions]: {{< url path="Cortex.GettingStarted.OnPremise.InstallInnovationOnly.SingleServerWithoutHA.Prerequisites" >}} +[Antivirus exclusions]: {{< url path="Cortex.GettingStarted.OnPremise.InstallInnovationOnly.SingleServerWithoutHA.Prerequisites" >}}#antivirus-exclusions [Items]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} [item]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Collections.Items.MainDoc" >}} ``` +```toml +# urls.toml — fragment is part of the value (never appended in the markdown footer) +AntivirusExclusions = "/docs/getting-started/on-premise/install-innovation-only/single-server-without-ha/prerequisites#antivirus-exclusions" +``` + ## Same-page `ref` links Links to headings or fragments **on the current page** use reference definitions with Hugo’s built-in `{{< ref "#slug" >}}`, then the same in-body pattern as `url` links. Pattern: `content/en/docs/2026.3/Reference/Concepts/working-with/concurrency/semaphores/what-is-a-semaphore.md`. -- **`ref` is `#slug` only.** Do not use `ref` with relative paths (`other-page.md`, `../foo.md#bar`, or `page.md#fragment`). Cross-page targets (including fragments on other pages) and external sites use `{{< url path="…" >}}` — look up or add a key in `data/urls.toml` per the `url` workflow above. +- **`ref` is `#slug` only.** Do not use `ref` with relative paths (`other-page.md`, `../foo.md#bar`, or `page.md#fragment`). Cross-page targets (including headings on other pages) and external sites use `{{< url path="…" >}}` with a `urls.toml` key that includes any `#fragment` — never append `#` after the shortcode. - **Never** write inline `[text](#slug)`. Always add a footer definition and reference it. - **One definition per anchor** on the page. Do not add a second `[OtherText]: {{< ref "#same-slug" >}}` for the same fragment. - Prefer a stable canonical label (usually the heading text form, e.g. `Queuing Semaphore`). diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index 44c526428..ea336463f 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -13,16 +13,17 @@ Read `layouts/shortcodes/*.html` before changing usage. | `ctx` | Renders CORTEX; not in `title`/`linkTitle` | | `version` | Uses `{version}/_shared/currentVersion.md` | | `section` | Path under `{version}/_shared/`; must exist | -| `url` | Keys from `data/urls.toml` (internal `Cortex.*` and external namespaces). Look up existing keys first; never add a second key for the same target URL. Never plain `[text](https://…)` or bare URLs | -| `ref` | Hugo built-in (not under `layouts/shortcodes/`). **Same-page `#slug` only**: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. **Never** relative paths (`page.md`, `../foo.md#bar`) — use `url` + `urls.toml` instead | +| `url` | Keys from `data/urls.toml` (internal `Cortex.*` and external namespaces). Prefer heading keys (URL includes `#slug`) when linking to a section. Look up existing keys first; never add a second key for the same full target URL (path ± `#fragment`). Never append `#…` after the shortcode. Never plain `[text](https://…)` or bare URLs | +| `ref` | Hugo built-in (not under `layouts/shortcodes/`). **Same-page `#slug` only**: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. **Never** relative paths (`page.md`, `../foo.md#bar`) — use `url` + a `urls.toml` key that includes any fragment | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | ### `url` key workflow 1. Grep content for an existing `{{< url path="…" >}}` to the target; copy that key exactly. -2. Search `data/urls.toml` for any key whose URL already targets that destination (same path ± trailing slash / `_index` for docs pages; exact `https://` / `http://` for external sites). +2. Search `data/urls.toml` for any key whose URL already targets that destination (same path ± trailing slash / `_index` for docs pages, **including exact `#fragment` when linking to a heading**; exact `https://` / `http://` for external sites). Page-without-fragment and `page#slug` are different destinations. 3. Do not invent a “normalized” key from the folder tree (or a new external namespace) when an atypical key already maps there. -4. Add a new entry only when none exists: brand-new docs pages get at most one `Cortex.*` key (sibling style); external URLs use matching sibling namespace style (`MSDocs.…`, `GitHub.…`, `Postman.…`, etc.). +4. Add a new entry only when none exists: brand-new docs pages get a page-level `Cortex.*` key (sibling style); heading destinations get additional sibling-style fragment keys (fragment in the `urls.toml` value); external URLs use matching sibling namespace style (`MSDocs.…`, `GitHub.…`, `Postman.…`, etc.). +5. Footer is always `[Label]: {{< url path="…" >}}` — never `}}#slug`. Template edits affect all versions — scope testing accordingly. diff --git a/.cursor/rules/security-cursorignore.mdc b/.cursor/rules/security-cursorignore.mdc index d420ef7e0..da20e694f 100644 --- a/.cursor/rules/security-cursorignore.mdc +++ b/.cursor/rules/security-cursorignore.mdc @@ -9,4 +9,4 @@ Follow [.cursorignore](../../.cursorignore). Do not read/commit: `config.*`, other `data/**` (except `data/urls.toml`), CI configs, `.env*`, certs/keys, `public/**`, `node_modules/**`, internal/draft content paths. -**Exception:** `data/urls.toml` may be read and updated for link keys. Do not read other ignored `data/**` or `*.toml` files. Look up existing keys in `data/urls.toml` before adding any new key (internal `Cortex.*` or external namespaces) — never invent a parallel key for a destination URL that already has one. +**Exception:** `data/urls.toml` may be read and updated for link keys. Do not read other ignored `data/**` or `*.toml` files. Look up existing keys in `data/urls.toml` before adding any new key (internal `Cortex.*` or external namespaces) — never invent a parallel key for a destination URL that already has one (full URL including any `#fragment`; page and heading are distinct destinations). diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index 42c79e896..ede54ae7a 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -16,7 +16,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec 2. Set `weight` relative to neighbors (lower = earlier in sidebar). 3. Use literal CORTEX in `title`/`linkTitle`; `{{% ctx %}}` in body. 4. For install/guide style, use `# {{% param title %}}` and match callouts (`pageinfo`, `alert`). -5. Add next-step / cross-page / external links with `{{< url path="…" >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key (internal `Cortex.*` or external namespaces such as `MSDocs.…`, `Postman.…`). Never write plain markdown external links (`[text](https://…)`) or bare URLs. Never add a second `urls.toml` entry for a destination that already has one. For a brand-new page, add at most one new `Cortex.*` key (sibling style); add external keys only when the destination URL is new. One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). Same-page headings: one `[Label]: {{< ref "#slug" >}}` per anchor; same exact-case / `[variant][canonical]` rules as `url` — never inline `[text](#slug)`. +5. Add next-step / cross-page / external links with `{{< url path="…" >}}` — reuse keys from sibling pages and look up [`data/urls.toml`](../../../data/urls.toml) before inventing any key (internal `Cortex.*` or external namespaces such as `MSDocs.…`, `Postman.…`). Never write plain markdown external links (`[text](https://…)`) or bare URLs. Never add a second `urls.toml` entry for a destination that already has one (full URL including any `#fragment`). For a brand-new page, add a page-level `Cortex.*` key (sibling style); when linking to a specific heading on this or another page, reuse or add a sibling-style fragment key (fragment in the `urls.toml` value — never append `#…` after the shortcode). Add external keys only when the destination URL is new. One footer definition per path; use `[variant][canonical]` when wording or case differs (exact case match on the shared label). Same-page headings: one `[Label]: {{< ref "#slug" >}}` per anchor; same exact-case / `[variant][canonical]` rules as `url` — never inline `[text](#slug)`. 6. If content repeats across pages, add `{version}/_shared/...` and `{{< section "/path.md" >}}` instead. 7. Do not edit other version trees unless requested. @@ -24,7 +24,7 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Frontmatter complete - [ ] Branding matches `ctx` convention -- [ ] Cross-page and external links: keys reused or looked up in `data/urls.toml`; no plain `[…](https://…)`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) +- [ ] Cross-page and external links: keys reused or looked up in `data/urls.toml`; section topics use heading keys (`#slug` in the value); no `}}#…` footers; no plain `[…](https://…)`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) - [ ] Same-page links: `ref` footers (`#slug`), not inline `[…](#…)`; one def per anchor; exact-case labels / `[variant][canonical]` -- [ ] At most one new `urls.toml` key if this page is new; external keys only when the URL is new +- [ ] New page gets a page-level `urls.toml` key; heading destinations get fragment keys as needed; external keys only when the URL is new - [ ] Scope limited to requested version diff --git a/AGENTS.md b/AGENTS.md index d45aab891..885f7182f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ For multi-page or cross-version changes, outline affected paths first (which ver ### Security and ignored paths -Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other `data/**`, CI workflows, secrets, or internal/draft paths. **Exception:** read and update [`data/urls.toml`](data/urls.toml) for link keys — look up existing keys first; do not add a second key that maps to the same destination (docs page or external URL). +Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other `data/**`, CI workflows, secrets, or internal/draft paths. **Exception:** read and update [`data/urls.toml`](data/urls.toml) for link keys — look up existing keys first; do not add a second key that maps to the same destination (full URL including any `#fragment`; page and heading are distinct destinations). ## Project agent assets @@ -50,6 +50,7 @@ Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other - Procedural, customer-facing tone; preserve frontmatter (`title`, `linkTitle`, `description`, `weight`). - Prefer shortcodes over raw HTML; match `{{< url path="…" >}}` for internal **and** external links (never plain `[text](https://…)` or bare URLs). +- Prefer linking to the relevant heading: same-page via `{{< ref "#slug" >}}`; cross-page via a `urls.toml` key whose URL includes `#slug` — never append `#…` after the `url` shortcode. Use a page-level key only for whole-page references. - Scope changes to the version(s) the user requested. ## What not to do From 9b34e4ecd6104279fee8619f0b9ca2c337419592 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 21 Jul 2026 13:33:51 +0100 Subject: [PATCH 07/14] Enhance documentation guidelines for link usage, specifying exceptions for alert links and reinforcing shortcode preferences across multiple files. --- .cursor/agents/docs-reviewer.md | 2 +- .cursor/agents/docs-writer.md | 1 + .cursor/commands/review-docs.md | 1 + .cursor/rules/hugo-content-markdown.mdc | 2 ++ .cursor/rules/hugo-shortcodes.mdc | 25 +++++++++++++++++++++++++ AGENTS.md | 2 +- 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 566c1bcca..57ab0ccdf 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -11,7 +11,7 @@ Check: 1. **Scope** — only intended version(s) and files touched. 2. **Conventions** — frontmatter, `ctx`, shortcodes, shared sections. -3. **Links/media** — `url`/`section`/`figure`/`ref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page, heading, or external URL as an atypical key). Prefer heading keys when link text/context names a section — flag page-level keys used for section topics, and flag any `{{< url path="…" >}}#…` footer (expect a dedicated `urls.toml` key with the fragment in the value). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. +3. **Links/media** — `url`/`section`/`figure`/`ref`/`ahref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page, heading, or external URL as an atypical key). Prefer heading keys when link text/context names a section — flag page-level keys used for section topics, and flag any `{{< url path="…" >}}#…` footer (expect a dedicated `urls.toml` key with the fragment in the value). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. Inside `alert`: expect `{{< ahref … >}}` only — flag reference-style / markdown links; flag alerts whose first body token is `ahref`. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index 16483400a..fe26e1b2b 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -14,4 +14,5 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Prefer heading destinations: when prose targets a section on another page, use (or add) a `urls.toml` key whose value includes `#slug`. Never append `#…` after the `url` shortcode. Use a page-level key only for whole-page references. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). - Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + a `urls.toml` key with any fragment in the value); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. +- Inside `alert` callouts: links use `{{< ahref path="…" title="…" >}}` (same `urls.toml` keys as `url`); never reference-style `url`/`ref` or markdown links inside alerts. Never start the alert body with a link — lead with plain text or reword/omit the link. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index bcae3b886..85bc1d883 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -14,5 +14,6 @@ Review current doc changes (branch diff or open files). 7. Heading links: when link text/context names a section, expect a `urls.toml` key whose value includes `#slug` — flag page-level keys used for section topics, and flag `{{< url path="…" >}}#…` footers (fragment belongs in `urls.toml`, not after the shortcode). 8. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. 9. External links: flag plain markdown `[…](https://…)` / `[…](http://…)` and bare URLs; expect `{{< url path="…" >}}` with a key looked up (or added) in `data/urls.toml`. +10. Alerts: links inside must use `{{< ahref path="…" title="…" >}}` — flag reference-style `url`/`ref`, inline `[…](#…)`, or plain URLs inside alerts. Flag alerts whose first body token is a link (`ahref`); expect plain-text lead-in or reword/omit. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index 487961ccf..bd2c2c2b0 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -15,6 +15,8 @@ alwaysApply: false Every navigable link—docs pages **and** third-party / external sites—uses a reference definition with `{{< url path="…" >}}`. Do **not** use plain markdown external links (`[text](https://…)`) or bare `https://…` / `http://…` URLs in body text. +**Exception: links inside alerts** — use `{{< ahref path="…" title="…" >}}` (not reference-style `url`/`ref`). The first word of the alert body cannot be a link; lead with plain text or reword/omit the link. See [hugo-shortcodes.mdc](hugo-shortcodes.mdc) (Alerts and `ahref`). + Keys live in [`data/urls.toml`](../../data/urls.toml): internal docs under `Cortex.*`, external destinations under other top-level namespaces (e.g. `MSDocs.…`, `GitHub.…`, `Postman.…`, `Wikipedia.…`, `W3.…`). ### Prefer heading keys diff --git a/.cursor/rules/hugo-shortcodes.mdc b/.cursor/rules/hugo-shortcodes.mdc index ea336463f..47e27d0e9 100644 --- a/.cursor/rules/hugo-shortcodes.mdc +++ b/.cursor/rules/hugo-shortcodes.mdc @@ -15,6 +15,8 @@ Read `layouts/shortcodes/*.html` before changing usage. | `section` | Path under `{version}/_shared/`; must exist | | `url` | Keys from `data/urls.toml` (internal `Cortex.*` and external namespaces). Prefer heading keys (URL includes `#slug`) when linking to a section. Look up existing keys first; never add a second key for the same full target URL (path ± `#fragment`). Never append `#…` after the shortcode. Never plain `[text](https://…)` or bare URLs | | `ref` | Hugo built-in (not under `layouts/shortcodes/`). **Same-page `#slug` only**: footer `[Label]: {{< ref "#slug" >}}` — never inline `[text](#slug)`. One def per `#slug`; exact-case labels / `[variant][canonical]` as for `url`. **Never** relative paths (`page.md`, `../foo.md#bar`) — use `url` + a `urls.toml` key that includes any fragment | +| `ahref` | Inline link for use **inside `alert` only**. Same `urls.toml` keys as `url`; display text via `title`. See [Alerts and `ahref`](#alerts-and-ahref) | +| `alert` | Callout. Prefer `{{% alert title="…" %}}` … `{{% /alert %}}` (match siblings; `color`/`type` as needed). Links inside: `ahref` only; first word of body cannot be a link | | `figure` | `src` under `/images/...`; file in `content/static/{version}/images/` | | `image` | `src` under `/images/...`; file in `content/static/{version}/images/` | @@ -26,4 +28,27 @@ Read `layouts/shortcodes/*.html` before changing usage. 4. Add a new entry only when none exists: brand-new docs pages get a page-level `Cortex.*` key (sibling style); heading destinations get additional sibling-style fragment keys (fragment in the `urls.toml` value); external URLs use matching sibling namespace style (`MSDocs.…`, `GitHub.…`, `Postman.…`, etc.). 5. Footer is always `[Label]: {{< url path="…" >}}` — never `}}#slug`. +### Alerts and `ahref` + +Prefer `{{% alert title="…" %}}` … `{{% /alert %}}` (match siblings; add `color`/`type` when siblings do). + +Links **inside** an alert use `{{< ahref path="…" title="…" >}}` only — same `urls.toml` key lookup as `url` (reuse / look up / no duplicate destination). Do **not** use reference-style `[text][]`, footer `url`/`ref`, inline `[text](#…)`, or plain `https://…` inside alerts. + +**First word cannot be a link.** The alert body must start with plain text (or omit the link / reword). Do not open the body with `{{< ahref … >}}`. + +```markdown +{{% alert title="Note" %}} +Some files might fail to be processed with `Access is denied`. This can be ignored. +{{% /alert %}} + +{{% alert title="Note" %}} +For details, see {{< ahref path="Cortex.Example.MainDoc" title="Prerequisites" >}}. +{{% /alert %}} + + +{{% alert title="Note" %}} +{{< ahref path="Cortex.Example.MainDoc" title="Prerequisites" >}} covers antivirus exclusions. +{{% /alert %}} +``` + Template edits affect all versions — scope testing accordingly. diff --git a/AGENTS.md b/AGENTS.md index 885f7182f..cebd8b326 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other ## Quality bar - Procedural, customer-facing tone; preserve frontmatter (`title`, `linkTitle`, `description`, `weight`). -- Prefer shortcodes over raw HTML; match `{{< url path="…" >}}` for internal **and** external links (never plain `[text](https://…)` or bare URLs). +- Prefer shortcodes over raw HTML; match `{{< url path="…" >}}` for internal **and** external links (never plain `[text](https://…)` or bare URLs). Exception: inside `alert`, use `{{< ahref path="…" title="…" >}}` — the first word of the alert body cannot be a link. - Prefer linking to the relevant heading: same-page via `{{< ref "#slug" >}}`; cross-page via a `urls.toml` key whose URL includes `#slug` — never append `#…` after the `url` shortcode. Use a page-level key only for whole-page references. - Scope changes to the version(s) the user requested. From 0e45f74bb69956b767a5976f7cace7333a6b64ca Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Wed, 22 Jul 2026 14:13:16 +0100 Subject: [PATCH 08/14] Enhance documentation guidelines for Remarks and Known Limitations across multiple files, clarifying structure and classification criteria. --- .cursor/agents/docs-reviewer.md | 1 + .cursor/agents/docs-writer.md | 1 + .cursor/commands/review-docs.md | 1 + .cursor/rules/hugo-content-markdown.mdc | 51 +++++++++++++++++++ .../skills/add-documentation-page/SKILL.md | 1 + AGENTS.md | 1 + content/AGENTS.md | 9 ++++ 7 files changed, 65 insertions(+) diff --git a/.cursor/agents/docs-reviewer.md b/.cursor/agents/docs-reviewer.md index 57ab0ccdf..9e1b8b6a9 100644 --- a/.cursor/agents/docs-reviewer.md +++ b/.cursor/agents/docs-reviewer.md @@ -14,5 +14,6 @@ Check: 3. **Links/media** — `url`/`section`/`figure`/`ref`/`ahref` paths likely valid. For new keys: confirm they were looked up in `data/urls.toml` and do not duplicate an existing target URL (flag invented “normalized” keys that point at the same page, heading, or external URL as an atypical key). Prefer heading keys when link text/context names a section — flag page-level keys used for section topics, and flag any `{{< url path="…" >}}#…` footer (expect a dedicated `urls.toml` key with the fragment in the value). Flag plain markdown external links `[…](https://…)` and bare `https://` / `http://` URLs — expect footer `[Label]: {{< url path="…" >}}` instead. For both `url` and `ref` footers: flag duplicate defs for the same target, and reference labels whose case does not match the definition (use `[Text][canonical]` instead). Flag inline same-page anchors `[…](#…)` — expect footer `[Label]: {{< ref "#slug" >}}` instead. Flag any `{{< ref "…" >}}` that is not a same-page `#slug` only (e.g. relative `.md` or `.md#fragment`) — expect `url` + `urls.toml` instead. Inside `alert`: expect `{{< ahref … >}}` only — flag reference-style / markdown links; flag alerts whose first body token is `ahref`. 4. **Security** — no secrets, config, or ignored paths in the diff (except legitimate `data/urls.toml` key adds). 5. **Consistency** — tone and structure match sibling pages. +6. **Remarks / Known Limitations** — flag general remarks (expected behavior, null/empty handling, scope notes) placed under `### Known Limitations`; those belong as their own `###` headings under `## Remarks`. Flag clear constraints/gaps that lack a Known Limitations placement when the page uses that section. Flag Known Limitations content that uses nested headings (`####`) instead of a bullet list. Flag top-level `## Known Limitations` when siblings nest it under Remarks. Provide prioritized findings: must-fix vs nice-to-have. diff --git a/.cursor/agents/docs-writer.md b/.cursor/agents/docs-writer.md index fe26e1b2b..633f50318 100644 --- a/.cursor/agents/docs-writer.md +++ b/.cursor/agents/docs-writer.md @@ -15,4 +15,5 @@ You write and edit CORTEX product documentation in this Hugo/Docsy repo. - Footer `url` defs: one per path; reuse that label via `[variant][canonical]` when wording or case differs; keep reference label case exact (avoids markdown warnings and duplicate shortcodes). - Same-page `ref` defs: `#slug` only (never relative `.md` / `.md#fragment` — those use `url` + a `urls.toml` key with any fragment in the value); one per `#slug`; same case rules as `url` — exact label match for `[Label][]`, otherwise `[variant][canonical]`; never inline `[text](#slug)`. - Inside `alert` callouts: links use `{{< ahref path="…" title="…" >}}` (same `urls.toml` keys as `url`); never reference-style `url`/`ref` or markdown links inside alerts. Never start the alert body with a link — lead with plain text or reword/omit the link. +- Remarks vs Known Limitations: classify each item. General functional notes (null/empty handling, scope, expected behavior) get their own `###` heading under `## Remarks`. Real constraints/gaps go only under `### Known Limitations` as a bullet list (no nested headings). If classification is unclear, ask the user before writing or moving the item — do not guess into Known Limitations. - Keep diffs minimal and limited to requested versions. diff --git a/.cursor/commands/review-docs.md b/.cursor/commands/review-docs.md index 85bc1d883..04c2ab4bf 100644 --- a/.cursor/commands/review-docs.md +++ b/.cursor/commands/review-docs.md @@ -15,5 +15,6 @@ Review current doc changes (branch diff or open files). 8. Same-page links: flag inline `[…](#…)` anchors; expect footer `[Label]: {{< ref "#slug" >}}` plus reference-style body links with the same exact-case rules as `url`. Flag `{{< ref "…" >}}` that is not `#slug`-only (relative `.md` / `.md#fragment`) — expect `url` + `urls.toml`. 9. External links: flag plain markdown `[…](https://…)` / `[…](http://…)` and bare URLs; expect `{{< url path="…" >}}` with a key looked up (or added) in `data/urls.toml`. 10. Alerts: links inside must use `{{< ahref path="…" title="…" >}}` — flag reference-style `url`/`ref`, inline `[…](#…)`, or plain URLs inside alerts. Flag alerts whose first body token is a link (`ahref`); expect plain-text lead-in or reword/omit. +11. Remarks / Known Limitations: flag general remarks (expected behavior, null/empty handling, scope notes) under `### Known Limitations` — expect their own `###` headings under `## Remarks`. Flag clear constraints dumped as plain remarks when siblings use Known Limitations. Flag Known Limitations that use nested headings (`####`) instead of a bullet list. Flag top-level `## Known Limitations` when siblings nest it under Remarks. Output: brief bullet list of issues and suggested fixes. diff --git a/.cursor/rules/hugo-content-markdown.mdc b/.cursor/rules/hugo-content-markdown.mdc index bd2c2c2b0..240b36ea1 100644 --- a/.cursor/rules/hugo-content-markdown.mdc +++ b/.cursor/rules/hugo-content-markdown.mdc @@ -93,3 +93,54 @@ Links to headings or fragments **on the current page** use reference definitions [Queuing Semaphore]: {{< ref "#queuing-semaphore" >}} [value type]: {{< ref "what-is-a-data-type.md#value-types" >}} ``` + +## Remarks and Known Limitations + +When a page has a Remarks section, use this structure (match siblings such as `content/en/docs/2026.3/Reference/Blocks/Text/contains-text/contains-text-block.md`): + +```markdown +## Remarks + +### +… + +### +… + +### Known Limitations + +- … +- … +``` + +Classify each item before placing it: + +| Kind | Placement | +| --- | --- | +| **Limitation** — a real constraint, gap, or unsupported/edge-case behavior (often “cannot”, “only supports”, “not evaluated as equal”, “may be removed in future”) | Under `### Known Limitations` as a **bullet list**. Do **not** use `####` or other nested headings under Known Limitations | +| **Remark** — general functional behavior, null/empty handling, inclusive indexes, scope notes, or other expected “how it works” clarifications | Own `###` heading under `## Remarks` — **not** under Known Limitations | +| **Unclear** | **Ask the user** whether the item is a remark or a limitation before writing or moving it | + +Do not put general remarks under Known Limitations. Do not invent a top-level `## Known Limitations` when siblings use `### Known Limitations` under Remarks. Do not nest headings under Known Limitations. + +```markdown + +## Remarks + +### Null or empty Text + +If [Text][Text Property] is `null` or empty (i.e. `""`), … is set to `false`. + +### Known Limitations + +- If [Search Options]… is `SearchOptions.Regex` … some characters … may not evaluate as equal. + + +## Remarks + +### Known Limitations + +#### Null or empty Text + +If [Text][Text Property] is `null` or empty, … is set to `false`. +``` diff --git a/.cursor/skills/add-documentation-page/SKILL.md b/.cursor/skills/add-documentation-page/SKILL.md index ede54ae7a..fe0c80212 100644 --- a/.cursor/skills/add-documentation-page/SKILL.md +++ b/.cursor/skills/add-documentation-page/SKILL.md @@ -27,4 +27,5 @@ description: Add a new Hugo documentation page under a versioned CORTEX docs sec - [ ] Cross-page and external links: keys reused or looked up in `data/urls.toml`; section topics use heading keys (`#slug` in the value); no `}}#…` footers; no plain `[…](https://…)`; no duplicate target URLs; footers dedupe by path with shared labels (exact case) - [ ] Same-page links: `ref` footers (`#slug`), not inline `[…](#…)`; one def per anchor; exact-case labels / `[variant][canonical]` - [ ] New page gets a page-level `urls.toml` key; heading destinations get fragment keys as needed; external keys only when the URL is new +- [ ] If the page has Remarks / Known Limitations: remarks are own `###` headings under `## Remarks`; only real constraints under `### Known Limitations` as a bullet list (no nested headings); ask the user when classification is unclear - [ ] Scope limited to requested version diff --git a/AGENTS.md b/AGENTS.md index cebd8b326..f47275a3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,6 +51,7 @@ Respect [.cursorignore](.cursorignore). Do not read or commit Hugo config, other - Procedural, customer-facing tone; preserve frontmatter (`title`, `linkTitle`, `description`, `weight`). - Prefer shortcodes over raw HTML; match `{{< url path="…" >}}` for internal **and** external links (never plain `[text](https://…)` or bare URLs). Exception: inside `alert`, use `{{< ahref path="…" title="…" >}}` — the first word of the alert body cannot be a link. - Prefer linking to the relevant heading: same-page via `{{< ref "#slug" >}}`; cross-page via a `urls.toml` key whose URL includes `#slug` — never append `#…` after the `url` shortcode. Use a page-level key only for whole-page references. +- Remarks vs Known Limitations: general functional notes get their own `###` under `## Remarks`; only real constraints go under `### Known Limitations` as a bullet list (no nested headings). If unclear, ask before placing the item. - Scope changes to the version(s) the user requested. ## What not to do diff --git a/content/AGENTS.md b/content/AGENTS.md index 4d844c0e6..8589dbbee 100644 --- a/content/AGENTS.md +++ b/content/AGENTS.md @@ -29,3 +29,12 @@ weight: 10 - Match siblings: `# {{% param title %}}`, `pageinfo`, `alert`, `figure`, `url`, `section`, `tab`/`tabpane`. - Search `{version}/_shared/` before duplicating paragraphs. - Do not edit other version folders unless explicitly asked. + +### Remarks and Known Limitations + +When a page uses these sections (match siblings such as `content/en/docs/2026.3/Reference/Blocks/Text/contains-text/contains-text-block.md`): + +- Structure: `## Remarks` → each general remark as its own `###` heading → `### Known Limitations` for real constraints only (bullet list; no nested headings). +- **Remark** — expected functional behavior (null/empty handling, scope, inclusive indexes, “how it works”). Own `###` under Remarks — never under Known Limitations. +- **Limitation** — real constraint, gap, or unsupported/edge-case behavior. Under `### Known Limitations` as a bullet list (no `####` or other subheadings). +- **Unclear** — ask whether the item is a remark or a limitation before writing or moving it. From 65cbfe0081adf22aeb45e1f0d2a506143d85ffc0 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Thu, 30 Jul 2026 15:40:13 +0100 Subject: [PATCH 09/14] Enhance documentation for "What is Text?" by providing a comprehensive overview and detailed sections on null, empty text, whitespace, string vs char, operators, comparisons, and related concepts. Update URLs for better navigation and reference. --- .../working-with/text/what-is-text.md | 307 +++++++++++++++--- data/urls.toml | 18 + 2 files changed, 286 insertions(+), 39 deletions(-) diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md index 0fee94a8e..90665e33f 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md @@ -1,68 +1,297 @@ --- title: "What is Text?" linkTitle: "What is Text?" -description: "Information regarding what text is." +description: "Overview of text in CORTEX: String and Char, null and empty values, comparison, search options, parsing, formatting, encoding, immutability, literals, and indexing." weight: 1 --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: - -- What is text - - null, string.Empty, and whitespace -- Difference between string and char -- Operators and Comparisons - - https://learn.microsoft.com/en-us/dotnet/standard/base-types/basic-string-operations - - Concatenation - - Equality - - Casing - - Ordinal - - Invariant Culture - - Current Culture - - https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings - - LiteralText vs Regex vs PatternMatching - - Links to LiteralText - - Links to Regex - - Links to PatternMatching -- Parsing text to a number -- String Formatting - - Date Formatting - - Number Formatting -- Encoding -- Immutability of strings https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#immutability-of-strings -- Literal, Verbatim and Interpolated Strings - - String escape sequences https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#string-escape-sequences -- Substrings and Indexing - -Links: - -- https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/ +In {{% ctx %}}, **text** is represented by the [String][] data type (`System.String`, alias `string`): an immutable sequence of Unicode [Char][] values. Text appears throughout flows as [variable][] values, [block property][] inputs and outputs, messages, paths, payloads, and [expressions][] in the [Expression Editor][]. + +| Concept | Meaning | +| --- | --- | +| [String][] | A sequence of characters (for example `"Hello"`) | +| [Char][] | A single Unicode character (for example `'H'`) | +| `null` | No string instance; default for an uninitialized [String][] variable | +| Empty text | A string with length `0` (`""` or `String.Empty`) | +| Whitespace | Characters such as space, tab, and newline (see [Empty Text and Whitespace][]) | + +Most Text blocks and properties use [String][]. Use [Char][] when an expression or API needs a single character. How text is compared, cased, formatted, and parsed often depends on [culture][Culture] and [StringComparison][] rules — see [Equality][], [Casing][], and [Formatting][]. + +## Null, empty text, and whitespace + +A [String][] variable can be: + +| State | Example | Notes | +| --- | --- | --- | +| `null` | Unassigned [String][], or explicitly `null` | Not the same as empty text. See [Null and Nullable Types][]. | +| Empty | `""` or `String.Empty` | Length is `0`; contains no characters. | +| Whitespace-only | `" "`, `"\t"`, `"\r\n"` | Not empty; length is greater than `0`. | + +Prefer `String.Empty` or `""` when you need a defined empty value. Treat `null`, empty, and whitespace as distinct cases when validating input. For definitions and differences, see [Empty Text and Whitespace][]. Blocks such as those under [Text][Text Blocks] can test for `null`, empty, or whitespace before further processing. + +## String vs Char + +| | [String][] | [Char][] | +| --- | --- | --- | +| Represents | Zero or more characters | Exactly one Unicode character | +| Literal syntax | Double quotes: `"abc"` | Single quotes: `'a'` | +| Type kind | [Reference type][reference types] (default `null`) | [Value type][value types] (default `'\0'`) | +| Typical use | Messages, paths, search text, formatted output | Single-character checks, casting from code points | + +A [String][] is a sequence of [Char][] values. Indexing a string (for example `"Cortex"[0]`) yields a [Char][]. Creating and converting strings is documented on [String][]; character literals and escapes are documented on [Char][] and in [Literal, verbatim, and interpolated strings][]. + +## Operators and comparisons + +### Concatenation + +Combine text with: + +* The `+` operator (for example `"Hello" + " " + "World"`) +* `String.Concat`, `String.Join`, or `String.Insert` in expressions +* Blocks such as [Add Text at Beginning][], [Join Text][], and related Add Text / Join Text blocks + +Because strings are [immutable][Immutability of strings], each concatenation produces a **new** string. For many small concatenations in a loop, prefer building the result once (for example with fewer concatenations, [Format Text With Values][], or interpolation) rather than appending repeatedly. See [String concatenation][] and [Basic string operations][]. + +### Equality and comparison types + +Text equality is not always a simple character-by-character match. [StringComparison][] values control culture and casing: + +| Comparison | Typical use | +| --- | --- | +| [Ordinal][] / [Ordinal Ignore Case][] | Identifiers, paths, machine-oriented matches; prefer when unsure | +| [Invariant Culture][Invariant Culture comparison] / [Invariant Culture Ignore Case][] | Culture-stable linguistic rules | +| [Current Culture][Current Culture comparison] / [Current Culture Ignore Case][] | User-facing text for the execution server's locale | + +Do **not** change case (for example `ToLower` / `ToUpper`) only to compare strings. Choose an appropriate [StringComparison][] (or ignore-case variant) instead. See [Equality][], [Casing][], and [Best practices for comparing strings in .NET][]. + +## Searching text: LiteralText, Regex, and PatternMatching + +Many Text blocks accept [SearchOptions][] to choose how search text ([TextToFind][]) is interpreted: + +| [SearchOptions][] value | Behaviour | Learn more | +| --- | --- | --- | +| [LiteralText][] | Exact character match of the search text | [SearchOptions][] | +| [Regex][SearchOptions Regex] | .NET regular expression match | [Regex Syntax][] | +| [PatternMatching][] | Simple wildcards (`*` / `?`) | [Pattern Matching Syntax][] | + +Use [LiteralText][] for fixed phrases, [PatternMatching][] for simple wildcards (for example file-style patterns), and [Regex][SearchOptions Regex] for full pattern languages. Pair search with a [Comparison Type][Equality] when the block exposes one. Examples appear on blocks such as [Contains All Text][] and [Contains Any Text][]. + +## Parsing text to a number + +Turning text into a number (`Parse`, `TryParse`, `Convert.ToInt32`, and similar) depends on [culture][Culture]: decimal and group separators must match the format provider. Prefer [Invariant Culture][] for machine-readable values shared between systems, and an explicit [Specific Culture][] when the source uses a regional format. + +For full guidance, examples, and block options, see [Converting Numbers and Text][]. Related: [What is a Number?][] and [Number Formatting][]. + +## String formatting + +Formatting turns values (numbers, dates, other objects) into display or composite [String][] output using format providers, templates, and specifiers. + +| Goal | See | +| --- | --- | +| Composite templates and format items in Text | [Formatting][] | +| Number patterns (`"N2"`, `"C"`, and so on) | [Number Formatting][] | +| Date and time patterns | [Date and Time Formatting][] | +| Objects to text (blocks and expressions) | [Converting Objects To Text][] | +| Blocks | [Format Text With Value][], [Format Text With Values][], [Convert Object To Text][] | + +In expressions, [interpolated strings][Interpolated Strings] and `String.Format` are common alternatives to formatting blocks. Culture choice matters for both display and round-tripping — see [Culture][] and [Formatting][]. + +## Encoding + +[Encoding][Working with Text - Encoding] maps between text ([String][]) and bytes (files, network payloads, APIs). Choose an encoding explicitly when reading or writing non-UTF-8 data, or when auto-detection is unreliable. Encode/decode blocks support formats such as Base64, URL, Hex, and HTML — see [Encoding][Working with Text - Encoding] and the [Encoding][Encoding data type] data type. + +For .NET background, see [Introduction to character encoding in .NET][]. + +## Immutability of strings + +[String][] values are **immutable**: after a string is created, its character sequence cannot change. Methods that appear to modify text (`Replace`, `Substring`, `ToUpper`, concatenation, and so on) return a **new** string; the original remains unchanged. + +Implications for flows: + +* Assigning the result of a text operation to a variable replaces the reference; it does not edit the previous string in place. +* Sharing the same string value across variables is safe from unexpected in-place edits. +* Building large results from many small edits allocates many intermediate strings; prefer fewer concatenations or formatting when performance matters. + +See [Immutability of strings (C#)][] and [Strings (C# programming guide)][]. + +## Literal, verbatim, and interpolated strings + +In the [Expression Editor][], create text with: + +| Form | Example | Notes | +| --- | --- | --- | +| Regular (quoted) literal | `"Line1\nLine2"` | Escape sequences are processed | +| [Verbatim][] literal | `@"C:\Temp\file.txt"` | Backslashes are literal; useful for paths and regex | +| [Interpolated][MS Interpolation] string | `$"Hello {($)Name}"` | Embeds expressions; see [Interpolated Strings][] | +| Interpolated verbatim | `$@"C:\Data\{($)File}"` | Combines `$` and `@` (either order) | + +In the [Literal Editor][], surrounding quotes are not required for [String][] properties; quotes you type become part of the value. See [Create a String][] and [String literals][]. + +### Escape sequences + +In regular string literals, `\` starts an escape sequence (for example `\n` newline, `\t` tab, `\\` backslash, `\"` quote, `\uXXXX` Unicode). Verbatim literals do not process these escapes (except doubled quotes for an embedded `"`). Full tables and warnings are in [String escape sequences][]. + +## Substrings and indexing + +Strings use **zero-based** indexes. Common operations: + +| Need | Expression examples | Blocks | +| --- | --- | --- | +| Character at index | `"Cortex"[0]` → `'C'` | — | +| Find position | `"Cortex".IndexOf("tex")` | [Get Index of Text][] | +| Slice by range | `Substring`, ranges/indexes where supported | [Get Text Between Indexes][], [Get Text at Beginning][], [Get Text at End][] | +| Length | `"Cortex".Length` → `6` | Related Get Length blocks under [Text][Text Blocks] | + +Indexes refer to [Char][] units (UTF-16 code units), not always to user-perceived graphemes. For ranges and index syntax in modern C#, see [Indices and ranges][]. For broader string APIs, see [Basic string operations][]. ## Remarks ### Known Limitations -TODO +None ## See Also ### Related Concepts -TODO +* [Empty Text and Whitespace][] — `null`, empty, and whitespace +* [Equality][] — [StringComparison][] and comparison types +* [Casing][] — case conversion and culture +* [Formatting][] — composite text formatting +* [Encoding][Working with Text - Encoding] — text and byte encodings +* [Regex Syntax][] — regular expression language +* [Pattern Matching Syntax][] — `*` / `?` patterns +* [Converting Objects To Text][] — objects to string representations +* [Converting Numbers and Text][] — parse and format numbers +* [Number Formatting][] — numeric format providers and templates +* [Date and Time Formatting][] — date/time format patterns +* [Culture][] — invariant, current, and specific cultures +* [Interpolated Strings][] — embedding values in expressions ### Related Data Types -TODO +* [String][] +* [Char][] +* [SearchOptions][] +* [StringComparison][] +* [StringComparer][] +* [Encoding][Encoding data type] +* [CultureInfo][] +* [IFormatProvider][] +* [TextToFind][] ### Related Blocks -TODO +* [Contains All Text][] / [Contains Any Text][] +* [Find Text][] / [Find All Text][] +* [Get Index of Text][] +* [Get Text Between Indexes][] / [Get Text at Beginning][] / [Get Text at End][] +* [Add Text at Beginning][] +* [Join Text][] +* [Split Text][] +* [Format Text With Value][] / [Format Text With Values][] +* [Convert Object To Text][] +* [Encode Text][] / [Decode Text][] +* [Convert to Upper Case][] / [Convert to Lower Case][] ### External Documentation -TODO +* [Strings (C# programming guide)][] +* [Immutability of strings (C#)][] +* [String escape sequences][] +* [String literals][] +* [Basic string operations][] +* [Best practices for comparing strings in .NET][] +* [Introduction to character encoding in .NET][] +* [Parsing numeric strings in .NET][] +* [System.String][] +* [System.Char][] +* [StringComparison][MS StringComparison] + +[Immutability of strings]: {{< ref "#immutability-of-strings" >}} +[Literal, verbatim, and interpolated strings]: {{< ref "#literal-verbatim-and-interpolated-strings" >}} + +[Empty Text and Whitespace]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.EmptyTextAndWhitespace.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} +[Ordinal]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.Ordinal" >}} +[Ordinal Ignore Case]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.OrdinalIgnoreCase" >}} +[Invariant Culture comparison]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.InvariantCulture" >}} +[Invariant Culture Ignore Case]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.InvariantCultureIgnoreCase" >}} +[Current Culture comparison]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.CurrentCulture" >}} +[Current Culture Ignore Case]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.CurrentCultureIgnoreCase" >}} +[Casing]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Casing.MainDoc" >}} +[Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Formatting.MainDoc" >}} +[Working with Text - Encoding]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Encoding.MainDoc" >}} +[Regex Syntax]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.RegexSyntax.MainDoc" >}} +[Pattern Matching Syntax]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.PatternMatchingSyntax.MainDoc" >}} +[Converting Objects To Text]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.ConvertingObjectsToText.MainDoc" >}} +[Converting Numbers and Text]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.ConvertingNumbersAndText.MainDoc" >}} +[What is a Number?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.WhatIsANumber.MainDoc" >}} +[Number Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.NumberFormatting.MainDoc" >}} +[Date and Time Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.DateAndTime.DateAndTimeFormatting.MainDoc" >}} +[Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.MainDoc" >}} +[Invariant Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture.MainDoc" >}} +[Specific Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.SpecificCultures.MainDoc" >}} +[Null and Nullable Types]: {{< url path="Cortex.Reference.Concepts.Fundamentals.DataTypes.NullAndNullableTypes.MainDoc" >}} +[reference types]: {{< url path="Cortex.Reference.Concepts.Fundamentals.DataTypes.WhatIsADataType.ReferenceTypes" >}} +[value types]: {{< url path="Cortex.Reference.Concepts.Fundamentals.DataTypes.WhatIsADataType.ValueTypes" >}} +[variable]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Variables.WhatIsAVariable.MainDoc" >}} +[block property]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.WhatIsABlockProperty.MainDoc" >}} +[expressions]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.Expressions" >}} +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[Interpolated Strings]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.InterpolatedStrings" >}} +[Literal Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.LiteralEditor.MainDoc" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[Create a String]: {{< url path="Cortex.Reference.DataTypes.Text.String.CreateNew" >}} +[Char]: {{< url path="Cortex.Reference.DataTypes.Text.Char.MainDoc" >}} +[SearchOptions]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.MainDoc" >}} +[LiteralText]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.LiteralText" >}} +[SearchOptions Regex]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.Regex" >}} +[PatternMatching]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.PatternMatching" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} +[StringComparer]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparer.MainDoc" >}} +[Encoding data type]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.MainDoc" >}} +[CultureInfo]: {{< url path="Cortex.Reference.DataTypes.Text.CultureInfo.MainDoc" >}} +[IFormatProvider]: {{< url path="Cortex.Reference.DataTypes.Text.IFormatProvider.MainDoc" >}} +[TextToFind]: {{< url path="Cortex.Reference.DataTypes.Text.TextToFind.MainDoc" >}} + +[Text Blocks]: {{< url path="Cortex.Reference.Blocks.Text.MainDoc" >}} +[Contains All Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAllText.MainDoc" >}} +[Contains Any Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAnyText.MainDoc" >}} +[Find Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindText.MainDoc" >}} +[Find All Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindAllText.MainDoc" >}} +[Get Index of Text]: {{< url path="Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText.MainDoc" >}} +[Get Text Between Indexes]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextBetweenIndexes.MainDoc" >}} +[Get Text at Beginning]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextAtBeginning.MainDoc" >}} +[Get Text at End]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextAtEnd.MainDoc" >}} +[Add Text at Beginning]: {{< url path="Cortex.Reference.Blocks.Text.AddText.AddTextAtBeginning.MainDoc" >}} +[Join Text]: {{< url path="Cortex.Reference.Blocks.Text.JoinText.JoinText.MainDoc" >}} +[Split Text]: {{< url path="Cortex.Reference.Blocks.Text.SplitText.SplitText.MainDoc" >}} +[Format Text With Value]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValue.MainDoc" >}} +[Format Text With Values]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValues.MainDoc" >}} +[Convert Object To Text]: {{< url path="Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText.MainDoc" >}} +[Encode Text]: {{< url path="Cortex.Reference.Blocks.Text.EncodeText.EncodeText.MainDoc" >}} +[Decode Text]: {{< url path="Cortex.Reference.Blocks.Text.DecodeText.DecodeText.MainDoc" >}} +[Convert to Upper Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToUpperCase.MainDoc" >}} +[Convert to Lower Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToLowerCase.MainDoc" >}} + +[Strings (C# programming guide)]: {{< url path="MSDocs.CSharp.Strings" >}} +[Immutability of strings (C#)]: {{< url path="MSDocs.CSharp.ImmutabilityOfStrings" >}} +[String escape sequences]: {{< url path="MSDocs.CSharp.EscapeSequences" >}} +[String literals]: {{< url path="MSDocs.CSharp.StringLiterals" >}} +[Verbatim]: {{< url path="MSDocs.CSharp.Verbatim" >}} +[MS Interpolation]: {{< url path="MSDocs.CSharp.Interpolation" >}} +[Indices and ranges]: {{< url path="MSDocs.CSharp.IndicesAndRanges" >}} +[Basic string operations]: {{< url path="MSDocs.DotNet.BaseTypes.BasicStringOperations" >}} +[Best practices for comparing strings in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.BestPracticesStrings" >}} +[Introduction to character encoding in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.CharacterEncodingIntroduction" >}} +[Parsing numeric strings in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.ParsingNumeric" >}} +[String concatenation]: {{< url path="MSDocs.DotNet.Api.System.String.ConcatGuide" >}} +[System.String]: {{< url path="MSDocs.DotNet.Api.System.String.MainDoc" >}} +[System.Char]: {{< url path="MSDocs.DotNet.Api.System.Char.MainDoc" >}} +[MS StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} diff --git a/data/urls.toml b/data/urls.toml index 9eeea7efe..aab28959f 100644 --- a/data/urls.toml +++ b/data/urls.toml @@ -1859,6 +1859,12 @@ MainDoc = "/docs/reference/concepts/working-with/loops/" [Cortex.Reference.Concepts.WorkingWith.Numbers] MainDoc = "/docs/reference/concepts/working-with/numbers/" + [Cortex.Reference.Concepts.WorkingWith.Numbers.WhatIsANumber] + MainDoc = "/docs/reference/concepts/working-with/numbers/what-is-a-number/" + [Cortex.Reference.Concepts.WorkingWith.Numbers.ConvertingNumbersAndText] + MainDoc = "/docs/reference/concepts/working-with/numbers/converting-numbers-and-text/" + [Cortex.Reference.Concepts.WorkingWith.Numbers.NumberFormatting] + MainDoc = "/docs/reference/concepts/working-with/numbers/number-formatting/" [Cortex.Reference.Concepts.WorkingWith.Objects] [Cortex.Reference.Concepts.WorkingWith.Objects.WhatIsAnObject] MainDoc = "/docs/reference/concepts/working-with/objects/what-is-an-object/" @@ -1876,6 +1882,8 @@ MainDoc = "/docs/reference/concepts/working-with/tasks/" [Cortex.Reference.Concepts.WorkingWith.Text] MainDoc = "/docs/reference/concepts/working-with/text/" + [Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText] + MainDoc = "/docs/reference/concepts/working-with/text/what-is-text/" [Cortex.Reference.Concepts.WorkingWith.Text.ConvertingObjectsToText] MainDoc = "/docs/reference/concepts/working-with/text/converting-objects-to-text" StringInterpolation = "/docs/reference/concepts/working-with/text/converting-objects-to-text/#string-interpolation" @@ -2326,6 +2334,8 @@ MainDoc = "/docs/reference/data-types/text/regex/capturedetails" [Cortex.Reference.DataTypes.Text.SearchOptions] MainDoc = "/docs/reference/data-types/text/searchoptions" + LiteralText = "/docs/reference/data-types/text/searchoptions/#literaltext" + Regex = "/docs/reference/data-types/text/searchoptions/#regex" PatternMatching = "/docs/reference/data-types/text/searchoptions/#patternmatching" [Cortex.Reference.DataTypes.Text.String] MainDoc = "/docs/reference/data-types/text/string" @@ -2907,6 +2917,9 @@ BooleanLogicalOperators = "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators" ComparisonOperators = "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/comparison-operators" EqualityOperators = "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/equality-operators" + Strings = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/" + ImmutabilityOfStrings = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#immutability-of-strings" + EscapeSequences = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#string-escape-sequences" Interpolation = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#string-interpolation" Verbatim = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#verbatim-string-literals" InterpolatedVerbatim = "https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#verbatim-string-interpolation" @@ -3099,6 +3112,11 @@ [MSDocs.DotNet.Api.System.Net] NetworkCredential = "https://learn.microsoft.com/en-us/dotnet/api/system.net.networkcredential" HttpStatusCode = "https://learn.microsoft.com/en-us/dotnet/api/system.net.httpstatuscode" + [MSDocs.DotNet.BaseTypes] + BasicStringOperations = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/basic-string-operations" + BestPracticesStrings = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings" + CharacterEncodingIntroduction = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding-introduction" + ParsingNumeric = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/parsing-numeric" [MSDocs.DotNet.Fundamentals] [MSDocs.DotNet.Fundamentals.RuntimeLibraries] CultureAndRegionInfoBuilder = "https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-globalization-cultureandregioninfobuilder" From ac96f04688604776f47e51c8010637483b7b929a Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Thu, 30 Jul 2026 16:01:50 +0100 Subject: [PATCH 10/14] Enhance documentation on text casing and culture info by expanding sections, adding best practices, and updating related URLs for improved navigation and reference. --- .../Concepts/working-with/text/casing.md | 277 ++++++++++++++---- data/urls.toml | 16 + 2 files changed, 231 insertions(+), 62 deletions(-) diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/casing.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/casing.md index 9a3581df0..ba2c6131e 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/casing.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/casing.md @@ -1,128 +1,281 @@ --- title: "Casing" linkTitle: "Casing" -description: "Information regarding text casing." +description: "How text casing works in CORTEX: common case styles, culture-aware conversion, best practices for comparison vs case change, and related blocks." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +**Casing** is how letter case is applied to text—for example converting a [String][] to upper case, lower case, or title case. In {{% ctx %}}, case conversion follows .NET rules and is controlled by a [CultureInfo][] value (the **Culture Info** property on casing blocks, or a culture argument in expressions). -- Best Practices - - Comparing strings - do not lower or upper pick a relevant culture or ignore case +Casing is **not** the same as comparing text. Do **not** call `ToLower` / `ToUpper` (or casing blocks) only to decide whether two strings match. Use an appropriate [StringComparison][] or ignore-case option instead—see [Equality][] and [Best practices for comparing strings in .NET][]. -Links: +| Goal | Prefer | +| --- | --- | +| Change display or stored case (for example `"Hello"` → `"HELLO"`) | [Convert To Upper Case][] / [Convert To Lower Case][] (or related casing blocks), or `ToUpper` / `ToLower` with an explicit culture | +| Compare or search regardless of case | [StringComparison][] ignore-case values (for example `OrdinalIgnoreCase`) — see [Equality][] | +| Security-sensitive or culture-independent case change | [Invariant Culture][Invariant Culture concept] (`CultureInfo.InvariantCulture`) | +| User-facing casing for a known locale | [Current Culture][Current Culture concept] or a [Specific Culture][Specific Cultures concept] | -- https://learn.microsoft.com/en-us/dotnet/core/extensions/performing-culture-insensitive-string-operations -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case#compare-strings-of-mixed-case +For culture types and when to use each, see [What is a Culture?][] and the sections under [Culture Info][] below. ## Common types of text casing -There are many different types of text casing. - -The table below lists some of the most common types of text casing: +There are many styles of text casing. The table below lists styles commonly used in {{% ctx %}} flows and in programming identifiers: -| Name | Example | Notes | -|-----------|----------------------------|-------------| +| Name | Example | Notes | +| --- | --- | --- | | lowercase | `"this is lowercase"` | All letters in all words are lower cased. | | UPPERCASE | `"THIS IS UPPERCASE"` | All letters in all words are capitalized. | -| Title Case | `"This Is Title Case"` | First letter in all words is capitalized, all other letters are lower cased; except for words that are entirely upper cased, such as acronyms, which remain upper cased; spaces and punctuation are preserved. | -| camelCase | `"thisIsCamelCase"` | First letter in all words (except the first) is capitalized, all other letters are lower cased, and all spaces and punctuation are removed. | -| PascalCase | `"ThisIsCamelCase"` | First letter in all words is capitalized, all other letters are lower cased, and all spaces and punctuation are removed. | +| Title Case | `"This Is Title Case"` | First letter in each word is capitalized; other letters are lower cased. Words that are entirely upper cased (for example acronyms) remain upper cased. Spaces and punctuation are preserved. | +| camelCase | `"thisIsCamelCase"` | First letter of each word except the first is capitalized; other letters are lower cased; spaces and punctuation are removed. | +| PascalCase | `"ThisIsPascalCase"` | First letter of each word is capitalized; other letters are lower cased; spaces and punctuation are removed. | -## Culture Info +{{% ctx %}} provides blocks for each of these styles under [Related Blocks][]. Title case follows .NET [TextInfo.ToTitleCase][] behaviour—see [Title case behaviour][]. + +## Changing case in flows + +### Using blocks + +Use the Convert To blocks when you need to transform text case in a flow: -Culture Info specifies the culture-specific casing rules used to determine how the case of text is changed. +| Block | Result style | +| --- | --- | +| [Convert To Lower Case][] | lowercase | +| [Convert To Upper Case][] | UPPERCASE | +| [Convert To Title Case][] | Title Case | +| [Convert To Camel Case][] | camelCase | +| [Convert To Pascal Case][] | PascalCase | -The table below lists the most common supported culture info: +Each block accepts **Text** and an optional **Culture Info**. The default **Culture Info** is `CultureInfo.InvariantCulture`. If **Culture Info** is `null`, it is treated as invariant. See each block's remarks for null or empty **Text** behaviour. -| Name | Text Value | Description | -|----------|-----------------|-------------| -| Invariant Culture | CultureInfo.InvariantCulture | Used to compare text using culture-sensitive sort rules and the [Invariant Culture][]. Case of the texts is considered when comparing. | -| Current Culture | CultureInfo.CurrentCulture | Used to compare text using culture-sensitive sort rules and the [Current Culture][]. Case of the texts is considered when comparing. | +### Using expressions + +In the [Expression Editor][], change case with culture-aware overloads so the culture is explicit: + +| Need | Example | +| --- | --- | +| Upper case (invariant) | `"Hello".ToUpper(CultureInfo.InvariantCulture)` | +| Lower case (current culture) | `"Hello".ToLower(CultureInfo.CurrentCulture)` | +| Upper case (specific culture) | `"indigo".ToUpper(new CultureInfo("tr-TR"))` | +| Title case | `CultureInfo.InvariantCulture.TextInfo.ToTitleCase("a tale of two cities")` | + +Parameterless `ToUpper()` / `ToLower()` use [Current Culture][Current Culture concept] by default in .NET. Prefer overloads that take a [CultureInfo][] so results do not depend on an implicit server locale. See [Perform culture-insensitive case changes][] and [Changing case in .NET][]. + +Because [String][] values are immutable, every case conversion returns a **new** string; assign the result to a variable (blocks that take an InputOutput **Text** property do this for you). See [Immutability of strings][]. + +## Culture Info -In addition to [Invariant][Invariant Culture] and [Current Culture][], there are two other types of culture that can be used: +**Culture Info** specifies the culture-specific casing rules used when changing case. It is represented by the [CultureInfo][] data type (`System.Globalization.CultureInfo`). -- [Specific Cultures][] -- [Custom Cultures][] +| Name | Typical value | Casing behaviour | +| --- | --- | --- | +| [Invariant Culture][] | `CultureInfo.InvariantCulture` | Culture-insensitive casing; same rules on every server | +| [Current Culture][] | `CultureInfo.CurrentCulture` | Culture-sensitive casing from the flow execution server's locale | +| [Specific Cultures][] | `new CultureInfo("en-GB")`, `new CultureInfo("tr-TR")`, … | Culture-sensitive casing for a named language–region | +| [Custom Cultures][] | `new CultureInfo("x-my-culture")` (after registration) | Culture-sensitive casing from a culture registered on Windows | -For more information about culture info, please see [CultureInfo][]. +For more information about culture types, see [What is a Culture?][] and [CultureInfo][MS CultureInfo]. ### Invariant Culture -For Invariant Culture, the casing rules used to determine how the case of text is changed are not culture-sensitive. +For [Invariant Culture][Invariant Culture concept], casing rules are **not** tied to the server's regional settings. Results stay consistent across development, test, and production servers. -TODO: +#### When to use -- Link to Working with Culture -> Invariant Culture -- When to use? If not sure what to choose? -- notes about invariant culture and a worked through example -- From https://learn.microsoft.com/en-us/dotnet/api/system.globalization.textinfo.toupper?view=net-6.0 +* Default for casing blocks when you do not set **Culture Info** +* Case changes used for internal keys, protocols, persistence, or **security decisions** +* Any flow that must not change behaviour when an administrator alters OS regional settings -If a security decision depends on a string comparison or a case-change operation, the application should use the InvariantCulture to ensure that the behavior is consistent regardless of the culture settings of the system. However, the invariant culture must be used only by processes that require culture-independent results, such as system services. Otherwise, it produces results that might be linguistically incorrect or culturally inappropriate. +#### When not to use + +* User-facing text that must follow a particular locale's casing conventions—use [Current Culture][Current Culture concept] or a [Specific Culture][Specific Cultures concept] instead + +Microsoft guidance: if a security decision depends on a string comparison or a case-change operation, use the invariant culture so behaviour is consistent regardless of system culture. Use invariant culture only where culture-independent results are required; otherwise results can be linguistically incorrect or culturally inappropriate for display. See [Invariant Culture][Invariant Culture concept] and [Performing culture-insensitive string operations][]. + +#### Example + +With **Culture Info** set to `CultureInfo.InvariantCulture` (or left at the block default), [Convert To Upper Case][] converts `"The quick brown fox"` to `"THE QUICK BROWN FOX"`. ### Current Culture -For Current Culture, the casing rules used to determine how the case of text is changed are culture-sensitive and based on the culture of the operating system the flow execution is running on. +For [Current Culture][Current Culture concept], casing rules are culture-sensitive and follow the operating system locale of the server that executes the flow (`CultureInfo.CurrentCulture`). + +#### When to use + +* Display casing should match the **server** locale, and that locale is known to match the audience +* You intentionally want “whatever this server is configured for” -TODO: +#### When not to use -- Link to Working with Culture -> Current Culture -- When to use? If not sure what to choose? -- Best practices - all OSes in cluster should be installed with same OS culture and settings etc. - should also be time sync'd -- Notes about current culture and a worked through example +* Security checks, stored identifiers, or protocols—use [Invariant Culture][Invariant Culture concept] or ordinal comparison +* Output must be identical on every server—use invariant or an explicit [Specific Culture][Specific Cultures concept] +* Gateway users may have a different locale than the execution server + +In a **cluster**, install the same OS culture and regional settings on every node that runs the flow (and keep servers time synchronised when dates and times are involved). See [Current Culture][Current Culture concept]. + +#### Example + +If the server current culture is Turkish (`tr-TR`), upper-casing `"indigo"` with `CultureInfo.CurrentCulture` can produce `"İNDİGO"` (dotted capital *İ*), which differs from invariant or `en-US` results (`"INDIGO"`). Always set **Culture Info** explicitly when that difference matters. ### Specific Cultures -TODO: +[Specific Cultures][Specific Cultures concept] each define their own casing rules for a language and region (for example `en-GB`, `en-US`, `tr-TR`). Create one with `new CultureInfo("tr-TR")` and pass it as **Culture Info**. + +#### When to use + +* Casing must follow a **known** locale regardless of server settings +* You need a culture with special case mappings (for example Turkish `i` / `ı`) for correct display or localised processing + +#### When not to use -- Talk about how there are a number of specific cultures, each with their own casing rules -- When to use? If not sure what to choose? -- Link to Working with Culture -> Specific Cultures -- For a list of cultures that can be returned on Windows systems see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c under The Language table (`Language tag` defines the code required to create the culture) +* Culture-independent storage or security—use [Invariant Culture][Invariant Culture concept] +* The culture might not be installed on every execution server (throws [CultureInfoNotFoundException][]) + +For predefined Windows culture names, see [Supported Culture Codes][] (the **Language tag** column is the string passed to `new CultureInfo(...)`). See [Specific Cultures][Specific Cultures concept]. + +#### Example (Turkish casing) + +| Expression | Typical result | +| --- | --- | +| `"indigo".ToUpper(new CultureInfo("en-US"))` | `"INDIGO"` | +| `"indigo".ToUpper(new CultureInfo("tr-TR"))` | `"İNDİGO"` | + +Turkish distinguishes dotted and dotless `I`; using the wrong culture for case change can break lookups that assumed English-style casing. ### Custom Cultures -TODO: +[Custom Cultures][Custom Cultures concept] are cultures registered on Windows (via administrative tooling) beyond the built-in set. After registration they supply their own `TextInfo` casing rules, the same way as specific cultures. + +#### When to use + +* Your organisation registers a custom or replacement culture and casing must follow that definition on every execution server -- Talk about how there can be custom cultures installed, each with their own casing rules -- When to use? If not sure what to choose? -- Link to Working with Culture -> Custom Cultures +#### When not to use + +* A standard specific culture is enough +* The culture is not registered on all cluster nodes—`new CultureInfo("…")` throws [CultureInfoNotFoundException][] + +Flows do not register custom cultures; administrators register them on each server. See [Custom Cultures][Custom Cultures concept]. ## Remarks +### Comparing text versus changing case + +Do **not** convert both sides of a comparison to lower or upper case to ignore case. That approach is culture-sensitive (unless you always use invariant culture), allocates extra strings, and is easy to get wrong across locales. + +Instead: + +* For equality or ordering of mixed-case text, use [StringComparison][] values such as `OrdinalIgnoreCase`, `InvariantCultureIgnoreCase`, or `CurrentCultureIgnoreCase`—see [Equality][] and [Compare strings of mixed case][] +* Use casing blocks or `ToUpper` / `ToLower` only when you need the **cased text** as output or stored data + +### Immutable strings + +Case conversion does not edit a [String][] in place. Methods and blocks produce a new string; the original value remains unchanged until you assign the result. See [Immutability of strings][]. + +### Title case behaviour + +[Convert To Title Case][] and `TextInfo.ToTitleCase` capitalize the first letter of each word and lower-case the rest, while leaving words that are entirely upper case unchanged (treated as acronyms). The conversion is culture-sensitive but **does not** always produce linguistically perfect title casing for every language—for example English title case often leaves short words such as “of” lower case, which `ToTitleCase` does not. See [Changing case in .NET][] and [TextInfo.ToTitleCase][]. + +### Defaults in casing blocks + +| Situation | Behaviour | +| --- | --- | +| **Culture Info** omitted or default | `CultureInfo.InvariantCulture` | +| **Culture Info** is `null` | Treated as `CultureInfo.InvariantCulture` | +| **Text** is `null` or `""` | No conversion; see each block's remarks | + ### Known Limitations -TODO +* Culture-sensitive casing ([Current Culture][Current Culture concept], [Specific Cultures][Specific Cultures concept], [Custom Cultures][Custom Cultures concept]) can differ between servers when OS culture, installed cultures, or registered custom cultures differ. +* [Current Culture][Current Culture concept] reflects the **execution server**, not the Gateway user's browser locale. +* A culture name that works on one machine may throw [CultureInfoNotFoundException][] on another if that culture is not installed or registered. Convert To casing blocks surface an invalid **Culture Info** as [InvalidPropertyValueException][] instead. ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings, immutability, and overview of text concepts +* [Equality][] — [StringComparison][] and comparing text without changing case +* [Formatting][] — composite formatting and format providers +* [What is a Culture?][] — culture types overview +* [Invariant Culture][Invariant Culture concept] +* [Current Culture][Current Culture concept] +* [Specific Cultures][Specific Cultures concept] +* [Custom Cultures][Custom Cultures concept] ### Related Data Types -TODO +* [String][] +* [Char][] +* [CultureInfo][] +* [StringComparison][] +* [StringComparer][] ### Related Blocks -TODO: - -- Convert to Uppercase -- etc +* [Convert To Lower Case][] +* [Convert To Upper Case][] +* [Convert To Title Case][] +* [Convert To Camel Case][] +* [Convert To Pascal Case][] ### External Documentation -TODO - -[Current Culture]: {{< ref "#current-culture" >}} +* [Changing case in .NET][] +* [Compare strings of mixed case][] +* [Perform culture-insensitive case changes][] +* [Performing culture-insensitive string operations][] +* [Best practices for comparing strings in .NET][] +* [TextInfo.ToUpper][] +* [TextInfo.ToTitleCase][] +* [Supported Culture Codes][] +* [System.Globalization.CultureInfo][MS CultureInfo] +* [StringComparison][MS StringComparison] + +[Culture Info]: {{< ref "#culture-info" >}} [Invariant Culture]: {{< ref "#invariant-culture" >}} +[Current Culture]: {{< ref "#current-culture" >}} [Specific Cultures]: {{< ref "#specific-cultures" >}} [Custom Cultures]: {{< ref "#custom-cultures" >}} - -[CultureInfo]: {{< url path="MSDocs.DotNet.Api.System.Globalization.CultureInfo" >}} +[Related Blocks]: {{< ref "#related-blocks" >}} +[Title case behaviour]: {{< ref "#title-case-behaviour" >}} +[Immutability of strings]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.ImmutabilityOfStrings" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} +[Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Formatting.MainDoc" >}} +[What is a Culture?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.WhatIsACulture.MainDoc" >}} +[Invariant Culture concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture.MainDoc" >}} +[Current Culture concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.CurrentCulture.MainDoc" >}} +[Specific Cultures concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.SpecificCultures.MainDoc" >}} +[Custom Cultures concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.CustomCultures.MainDoc" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[Char]: {{< url path="Cortex.Reference.DataTypes.Text.Char.MainDoc" >}} +[CultureInfo]: {{< url path="Cortex.Reference.DataTypes.Text.CultureInfo.MainDoc" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} +[StringComparer]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparer.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} + +[Convert To Lower Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToLowerCase.MainDoc" >}} +[Convert To Upper Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToUpperCase.MainDoc" >}} +[Convert To Title Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToTitleCase.MainDoc" >}} +[Convert To Camel Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToCamelCase.MainDoc" >}} +[Convert To Pascal Case]: {{< url path="Cortex.Reference.Blocks.Text.ConvertTo.ConvertToPascalCase.MainDoc" >}} + +[Changing case in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.ChangingCase.MainDoc" >}} +[Compare strings of mixed case]: {{< url path="MSDocs.DotNet.BaseTypes.ChangingCase.CompareStringsOfMixedCase" >}} +[Perform culture-insensitive case changes]: {{< url path="MSDocs.DotNet.Core.Extensions.CultureInsensitiveCaseChanges" >}} +[Performing culture-insensitive string operations]: {{< url path="MSDocs.DotNet.Core.Extensions.CultureInsensitiveStringOperations" >}} +[Best practices for comparing strings in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.BestPracticesStrings" >}} +[TextInfo.ToUpper]: {{< url path="MSDocs.DotNet.Api.System.Globalization.TextInfo.ToUpper" >}} +[TextInfo.ToTitleCase]: {{< url path="MSDocs.DotNet.Api.System.Globalization.TextInfo.ToTitleCase" >}} +[Supported Culture Codes]: {{< url path="MSDocs.CSharp.SupportedCultureCodes" >}} +[MS CultureInfo]: {{< url path="MSDocs.DotNet.Api.System.Globalization.CultureInfo" >}} +[MS StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} +[CultureInfoNotFoundException]: {{< url path="MSDocs.DotNet.Api.System.Globalization.CultureInfoNotFoundException" >}} +[InvalidPropertyValueException]: {{< url path="Cortex.Reference.Exceptions.Flows.Blocks.InvalidPropertyValueException.MainDoc" >}} diff --git a/data/urls.toml b/data/urls.toml index aab28959f..f9d8f6bf7 100644 --- a/data/urls.toml +++ b/data/urls.toml @@ -1806,12 +1806,16 @@ MultipleConcurrencyLimits = "/docs/reference/concepts/working-with/concurrency/semaphores/what-is-a-semaphore/#multiple-concurrency-limits" [Cortex.Reference.Concepts.WorkingWith.Culture] MainDoc = "/docs/reference/concepts/working-with/culture/" + [Cortex.Reference.Concepts.WorkingWith.Culture.WhatIsACulture] + MainDoc = "/docs/reference/concepts/working-with/culture/what-is-a-culture/" [Cortex.Reference.Concepts.WorkingWith.Culture.CurrentCulture] MainDoc = "/docs/reference/concepts/working-with/culture/current-culture" [Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture] MainDoc = "/docs/reference/concepts/working-with/culture/invariant-culture" [Cortex.Reference.Concepts.WorkingWith.Culture.SpecificCultures] MainDoc = "/docs/reference/concepts/working-with/culture/specific-cultures" + [Cortex.Reference.Concepts.WorkingWith.Culture.CustomCultures] + MainDoc = "/docs/reference/concepts/working-with/culture/custom-cultures" [Cortex.Reference.Concepts.WorkingWith.DataSources] MainDoc = "/docs/reference/concepts/working-with/data-sources/" [Cortex.Reference.Concepts.WorkingWith.DataSources.SupportedDataSources] @@ -1884,6 +1888,7 @@ MainDoc = "/docs/reference/concepts/working-with/text/" [Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText] MainDoc = "/docs/reference/concepts/working-with/text/what-is-text/" + ImmutabilityOfStrings = "/docs/reference/concepts/working-with/text/what-is-text/#immutability-of-strings" [Cortex.Reference.Concepts.WorkingWith.Text.ConvertingObjectsToText] MainDoc = "/docs/reference/concepts/working-with/text/converting-objects-to-text" StringInterpolation = "/docs/reference/concepts/working-with/text/converting-objects-to-text/#string-interpolation" @@ -3100,6 +3105,10 @@ CultureInfoNotFoundException = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.culturenotfoundexception" CultureInfoParent = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.parent" CurrentCulture = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.currentculture" + [MSDocs.DotNet.Api.System.Globalization.TextInfo] + MainDoc = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.textinfo" + ToUpper = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.textinfo.toupper" + ToTitleCase = "https://learn.microsoft.com/en-us/dotnet/api/system.globalization.textinfo.totitlecase" [MSDocs.DotNet.Api.System.Text] Encoding = "https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding" UnicodeEncoding = "https://learn.microsoft.com/en-us/dotnet/api/system.text.unicodeencoding" @@ -3117,6 +3126,13 @@ BestPracticesStrings = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings" CharacterEncodingIntroduction = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding-introduction" ParsingNumeric = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/parsing-numeric" + [MSDocs.DotNet.BaseTypes.ChangingCase] + MainDoc = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case" + CompareStringsOfMixedCase = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case#compare-strings-of-mixed-case" + [MSDocs.DotNet.Core] + [MSDocs.DotNet.Core.Extensions] + CultureInsensitiveCaseChanges = "https://learn.microsoft.com/en-us/dotnet/core/extensions/performing-culture-insensitive-case-changes" + CultureInsensitiveStringOperations = "https://learn.microsoft.com/en-us/dotnet/core/extensions/performing-culture-insensitive-string-operations" [MSDocs.DotNet.Fundamentals] [MSDocs.DotNet.Fundamentals.RuntimeLibraries] CultureAndRegionInfoBuilder = "https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-globalization-cultureandregioninfobuilder" From 27f00916a19b7a671db3afaca932c0a2de2bca76 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Tue, 4 Aug 2026 11:03:03 +0100 Subject: [PATCH 11/14] added additional Working with Text content --- .../text/converting-objects-to-text.md | 235 +++++++++-- .../text/empty-text-and-whitespace.md | 195 ++++++++- .../Concepts/working-with/text/encoding.md | 209 +++++++++- .../Concepts/working-with/text/equality.md | 321 ++++++++++++--- .../Concepts/working-with/text/formatting.md | 267 +++++++++--- .../text/pattern-matching-syntax.md | 158 +++++++- .../working-with/text/regex-syntax.md | 382 +++++++++++------- .../data-types/text/stringcomparison.md | 6 +- data/urls.toml | 37 ++ 9 files changed, 1465 insertions(+), 345 deletions(-) diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/converting-objects-to-text.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/converting-objects-to-text.md index 289cbf121..9ec26b5e9 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/converting-objects-to-text.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/converting-objects-to-text.md @@ -1,87 +1,234 @@ --- title: "Converting Objects To Text" linkTitle: "Converting Objects To Text" -description: "Information regarding converting objects to their text representation." +description: "How values become text in CORTEX: ToString, Convert.ToString, Convert Object To Text, formatting and join blocks, Convert Object To Json, and expression alternatives." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO +Flows often need a [String][] representation of a value that is not already text—for example a number, [Boolean][], [DateTime][], [Structure][], or another object. In {{% ctx %}}, that conversion is the value's **text representation**: the result of formatting or calling `ToString` / `Convert.ToString`, depending on the block or expression you use. + +| Goal | Prefer | +| --- | --- | +| Message text from an object's named properties | [Convert Object To Text][] with a [format template][Formatting] | +| Composite template with index placeholders (`{0}`, `{1}`, …) | [Format Text With Value][] / [Format Text With Values][], or [String.Format][] / [string interpolation][String interpolation] | +| Join many values with a separator | [Join Text][] | +| Culture-aware date/time display | [Convert Date Time To Text][] — see [Date and Time Formatting][] | +| Structural / interchange text (JSON) | [Convert Object To Json][] | +| Quick conversion in an [expression][] | `ToString()`, `Convert.ToString`, or `$"…"` | + +For numeric parse/format patterns, see [Converting Numbers and Text][] and [Number Formatting][]. For composite format templates and format items in general, see [Formatting][]. + +## How values become text + +Most value types and many framework types override [Object.ToString][] (or overload it with format and [IFormatProvider][] arguments) so the result is a readable [String][]. Parameterless `ToString()` and many `Convert.ToString` overloads that take no provider use [Current Culture][] in .NET. Formatting blocks that expose **Format Provider** typically default to [Invariant Culture][] when the provider is omitted or `null`. + +When a formatting block replaces a placeholder with a non-text value, it converts that value to its text representation (type-specific `ToString` / formatting rules), controlled by the block's **Format Provider** where applicable. [Join Text][] always uses `value.ToString()` for non-text items. + +Per-type examples live on the data type pages under headings such as **Convert … to Text**—for example [Boolean][], [Int32][], [Double][], and [DateTime][]. Those tables show common expression forms (`ToString`, `Convert.ToString`) and blocks ([Convert Object To Text][], [Convert Object To Json][]). + +Some types do not override `ToString` with a content-aware representation. For those, [Object.ToString][] returns the type's **Full Name** (for example `"Cortex.DataTypes.Data.Commands"`). [Convert Object To Text][] with an empty format template uses `Convert.ToString` and shows the same Full Name behaviour—it does **not** fall back to JSON. Use [Convert Object To Json][] when you need a structural representation of the data. See [Types that return their Full Name][]. + +## Using Blocks + +| Block | How it produces text | +| --- | --- | +| [Convert Object To Text][] | Replaces `{Property}` tokens in a format template with object property values, or `Convert.ToString` when the template is empty | +| [Format Text With Value][] | Replaces `{0}` in a format template with a single value | +| [Format Text With Values][] | Replaces `{0}`, `{1}`, … with values from a collection | +| [Join Text][] | Joins values with a separator; non-text values use `value.ToString()` | +| [Convert Date Time To Text][] | Formats a date/time value with an optional format template and provider | +| [Convert Object To Json][] | Serializes the object to a JSON [String][] via [JsonSerializerSettings][] | + +Non-text values passed into [Format Text With Value][], [Format Text With Values][], and property placeholders in [Convert Object To Text][] are converted to their text representation. Template syntax, format items, and specifiers are covered under [Formatting][]; number and date/time domains are detailed in [Number Formatting][] and [Date and Time Formatting][]. + +### Convert Object To Text + +[Convert Object To Text][] builds text from an **Object** and an optional **Format Template** and **Format Provider**: + +1. If **Format Template** contains `{Property}` format parameters, each matching property name on the object is replaced with that property's value. Names are **case-sensitive** and must match exactly. Nested paths such as `{PaidOff.Total}` are supported. +2. Property values need not be text; non-text values are converted to their text representation (see [How values become text][]). +3. Format parameters can include format specifiers (for example `{LastPaymentAmount:C2}`). Specifiers follow [Formatting][], [Number Formatting][], and [Date and Time Formatting][]. +4. If **Format Template** is omitted, `null`, or empty (`""`), the result is [`Convert.ToString(Object, Format Provider)`][Convert.ToString(object, IFormatProvider)]. +5. If **Format Provider** is omitted or `null`, `CultureInfo.InvariantCulture` is used—see [Invariant Culture][]. +6. If **Format Template** is non-empty but contains **no** `{Property}` tokens, nothing is replaced; the result is the template string unchanged. +7. Indexing into properties (for example `ListProperty[0]` or `DictionaryProperty["key"]`) is not supported—see [Known Limitations][]. + +Worked examples are on the [Convert Object To Text][] block page. + +### Convert Object To Json + +[Convert Object To Json][] serializes an object to a JSON [String][], optionally controlled by [JsonSerializerSettings][] (null handling, date formats, escaping, type information, and related options). + +Use this block when you need a **structural** text representation—payload exchange, persistence as JSON, or when `ToString` / [Convert Object To Text][] (empty template) would only return a type **Full Name**. JSON output is not the same as culture-formatted display text; for display messages prefer format templates or the Format Text blocks. + +## Using Expressions + +In the [Expression Editor][], convert values to text with methods and string composition. + +### ToString() -### Using Blocks +Call `ToString()` on a value (or a [variable][] such as `($)Amount.ToString()`): -TODO: +| Form | Culture / provider | Typical use | +| --- | --- | --- | +| `value.ToString()` | [Current Culture][] when no provider is passed | Quick display; culture may vary by execution server | +| `value.ToString(format)` | [Current Culture][] | Numeric or date format strings—see [Number Formatting][] / [Date and Time Formatting][] | +| `value.ToString(format, provider)` | Explicit [IFormatProvider][] / [CultureInfo][] | Stable or locale-specific output | -- Explain that blocks can be used - maybe with examples or link to examples within the block documentation - - Format Text With Value - - Format Text With Values - - Join Text - - Convert Date Time To Text - - Convert Object To Text - need to make it clear how this works - does tostring and if tostring returns class name does json serialisation - - Convert Object To Json - need to make it clear how this works - does json serialisation +See [Object.ToString][] and the per-type **Convert … to Text** tables (for example [Int32.ToString][], [Boolean.ToString][], [DateTime.ToString][]). -### Using Expressions +### Convert.ToString -#### ToString() +[`Convert.ToString`][Convert.ToString] converts many source types to [String][]. The overload used by [Convert Object To Text][] when the template is empty is [`Convert.ToString(object, IFormatProvider)`][Convert.ToString(object, IFormatProvider)]. Prefer an explicit provider when the result must not depend on [Current Culture][]. -TODO: +### String interpolation -- .ToString() - talk about that some objects will just return their name, text formatting format providers etc. -- Convert.ToString() -- Examples and where to find in Data Types documentation +An [interpolated string][Interpolated Strings] is declared with `$` and embeds expressions in `{…}`: -#### String interpolation +```csharp +$"Hello {($)Name}" +$"Total: {($)Amount:C2}" +``` -See [Interpolated Strings][]. +Interpolation converts embedded values to text as part of composing the string. Use format and alignment clauses where needed (for example `{($)Amount:N2}`). For syntax and {{% ctx %}} expression usage, see [Interpolated Strings][] and [string interpolation in C#][MS Interpolation]. -#### String.Format() +### String.Format -TODO: String.Format +[`String.Format`][String.Format] applies composite formatting with index placeholders: + +```csharp +String.Format("Hello {0}", ($)Name) +String.Format(CultureInfo.InvariantCulture, "Total: {0:C2}", ($)Amount) +``` + +This matches the `{0}`, `{1}`, … style used by [Format Text With Value][] and [Format Text With Values][]. Overloads that take an [IFormatProvider][] keep culture explicit. See [String.Format][] and [Formatting][]. + +## Choosing an approach + +| Situation | Approach | +| --- | --- | +| Readable message from a [Structure][] or object properties | [Convert Object To Text][] with `{Property}` tokens | +| One or more values into a numbered template | [Format Text With Value][] / [Format Text With Values][], `String.Format`, or [string interpolation][String interpolation] | +| List of values separated by a delimiter | [Join Text][] | +| Date/time-specific formats | [Convert Date Time To Text][] + [Date and Time Formatting][] | +| Need JSON structure, or `ToString` only gives a Full Name | [Convert Object To Json][] | +| Inline conversion next to other expression logic | `ToString` / `Convert.ToString` / `$"…"` | + +Do **not** assume [Convert Object To Text][] serializes to JSON when the text representation is unhelpful—use [Convert Object To Json][] for that purpose. ## Remarks -### Known Limitations +### Culture and format provider defaults -#### Support for {{}} is Missing +| Context | Typical default when provider is omitted | +| --- | --- | +| Expression `ToString()` / many `Convert.ToString` overloads without a provider | [Current Culture][] | +| [Convert Object To Text][], [Format Text With Value][], [Format Text With Values][] (**Format Provider** omitted or `null`) | [Invariant Culture][] (`CultureInfo.InvariantCulture`) | +| [Convert Date Time To Text][] (**Format Provider** omitted or `null`) | [Invariant Culture][]; if **Format Template** is also empty or unspecified, the result uses the [ISO 8601][] pattern — see the block remarks | +| [Join Text][] | `value.ToString()` (no separate format provider property) | -Using `{{VariableName}}` expression syntax to convert a [variable][] to its [String][] representation is currently not supported. +Mixing expression defaults and block defaults for the same number or date can produce different separators or layouts. Set the provider explicitly when results must be stable across servers or match a known locale—see [What is a Culture?][], [Invariant Culture][], and [Current Culture][]. -It is possible to convert a [variable][] to its string representation within an [expression][] by using the `ToString()` [method][] (e.g. `($)VariableName.ToString()`) +### Types that return their Full Name -In future this limitation may be removed. +If a type does not provide a content-aware `ToString` override, [Object.ToString][] returns the runtime type's Full Name. [Convert Object To Text][] with an empty or unspecified format template follows `Convert.ToString` and exhibits the same behaviour. Data type pages document this where it applies (for example [Commands][]). To get property values or nested data as text, supply a format template with `{Property}` tokens, or use [Convert Object To Json][] for a JSON string. -[variable]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Variables.WhatIsAVariable.MainDoc" >}} -[expression]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.Expressions" >}} -[method]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MethodExpressions" >}} -[Interpolated Strings]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.InterpolatedStrings" >}} +### Known Limitations -[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +* Using `{{VariableName}}` expression syntax to convert a [variable][] to its [String][] representation is not supported. Use a [method][] call instead—for example `($)VariableName.ToString()`. In future this limitation may be removed. +* [Convert Object To Text][] does not support indexing into properties (for example `ListProperty[0]` or `DictionaryProperty["key"]`). See the block's [known limitations][Convert Object To Text Known Limitations]. ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings, literals, and overview of text concepts +* [Formatting][] — composite text formatting and format templates +* [Converting Numbers and Text][] — number ↔ text +* [Number Formatting][] — numeric format providers and templates +* [Date and Time Formatting][] — date/time format patterns +* [What is a Culture?][] — invariant, current, and specific cultures +* [Interpolated Strings][] — embedding values in the [Expression Editor][] ### Related Data Types -TODO +* [String][] +* [Boolean][] +* [Int32][] +* [Double][] +* [DateTime][] +* [Structure][] +* [CultureInfo][] +* [IFormatProvider][] +* [JsonSerializerSettings][] ### Related Blocks -TODO: - -- Format Text With Value -- Format Text With Values -- Join Text -- Convert Date Time To Text -- Convert Object To Text - need to make it clear how this works - does tostring and if tostring returns class name does json serialisation -- Convert Object To Json - need to make it clear how this works - does json serialisation +* [Convert Object To Text][] +* [Convert Object To Json][] +* [Format Text With Value][] +* [Format Text With Values][] +* [Join Text][] +* [Convert Date Time To Text][] ### External Documentation -TODO +* [Object.ToString][] +* [Convert.ToString][] +* [Convert.ToString(object, IFormatProvider)][] +* [String.Format][] +* [String interpolation in C#][MS Interpolation] +* [IFormatProvider][MS IFormatProvider] + +[How values become text]: {{< ref "#how-values-become-text" >}} +[String interpolation]: {{< ref "#string-interpolation" >}} +[Types that return their Full Name]: {{< ref "#types-that-return-their-full-name" >}} +[Known Limitations]: {{< ref "#known-limitations" >}} +[Convert Object To Text Known Limitations]: {{< url path="Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText.KnownLimitations" >}} +[ISO 8601]: {{< url path="Cortex.Reference.Concepts.WorkingWith.DateAndTime.DateAndTimeFormatting.ISO8601Standard" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Formatting.MainDoc" >}} +[Converting Numbers and Text]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.ConvertingNumbersAndText.MainDoc" >}} +[Number Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.NumberFormatting.MainDoc" >}} +[Date and Time Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.DateAndTime.DateAndTimeFormatting.MainDoc" >}} +[What is a Culture?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.WhatIsACulture.MainDoc" >}} +[Invariant Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture.MainDoc" >}} +[Current Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.CurrentCulture.MainDoc" >}} + +[variable]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Variables.WhatIsAVariable.MainDoc" >}} +[expression]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.Expressions" >}} +[method]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MethodExpressions" >}} +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[Interpolated Strings]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.InterpolatedStrings" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[Boolean]: {{< url path="Cortex.Reference.DataTypes.ConditionalLogic.Boolean.MainDoc" >}} +[Int32]: {{< url path="Cortex.Reference.DataTypes.Numbers.Int32.MainDoc" >}} +[Double]: {{< url path="Cortex.Reference.DataTypes.Numbers.Double.MainDoc" >}} +[DateTime]: {{< url path="Cortex.Reference.DataTypes.DateAndTime.DateTime.MainDoc" >}} +[Structure]: {{< url path="Cortex.Reference.DataTypes.Collections.Structure.MainDoc" >}} +[CultureInfo]: {{< url path="Cortex.Reference.DataTypes.Text.CultureInfo.MainDoc" >}} +[IFormatProvider]: {{< url path="Cortex.Reference.DataTypes.Text.IFormatProvider.MainDoc" >}} +[JsonSerializerSettings]: {{< url path="Cortex.Reference.DataTypes.Json.JsonSerializerSettings.MainDoc" >}} +[Commands]: {{< url path="Cortex.Reference.DataTypes.Data.Commands.MainDoc" >}} + +[Convert Object To Text]: {{< url path="Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText.MainDoc" >}} +[Convert Object To Json]: {{< url path="Cortex.Reference.Blocks.Json.ConvertJson.ConvertObjectToJson.MainDoc" >}} +[Format Text With Value]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValue.MainDoc" >}} +[Format Text With Values]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValues.MainDoc" >}} +[Join Text]: {{< url path="Cortex.Reference.Blocks.Text.JoinText.JoinText.MainDoc" >}} +[Convert Date Time To Text]: {{< url path="Cortex.Reference.Blocks.DateAndTime.ConvertDateTime.ConvertDateTimeToText.MainDoc" >}} + +[Object.ToString]: {{< url path="MSDocs.DotNet.Api.System.Object.ToString" >}} +[Convert.ToString]: {{< url path="MSDocs.DotNet.Api.System.Convert.ToString" >}} +[Convert.ToString(object, IFormatProvider)]: {{< url path="MSDocs.DotNet.Api.System.Convert.ToStringObjectFormatProvider" >}} +[String.Format]: {{< url path="MSDocs.DotNet.Api.System.String.Format" >}} +[MS Interpolation]: {{< url path="MSDocs.CSharp.Interpolation" >}} +[MS IFormatProvider]: {{< url path="MSDocs.DotNet.Api.System.IFormatProvider" >}} +[Int32.ToString]: {{< url path="MSDocs.DotNet.Api.System.Int32.ToString" >}} +[Boolean.ToString]: {{< url path="MSDocs.DotNet.Api.System.Boolean.ToString" >}} +[DateTime.ToString]: {{< url path="MSDocs.DotNet.Api.System.DateTime.ToString" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md index b96d91918..e58e66605 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md @@ -1,51 +1,214 @@ --- title: "Empty Text and Whitespace" linkTitle: "Empty Text and Whitespace" -description: "Information regarding empty text and whitespace." +description: "How empty text and whitespace work in CORTEX: null vs empty vs whitespace-only strings, common whitespace characters, and related Is Text blocks." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +In {{% ctx %}}, a [String][] can be missing (`null`), empty (length `0`), or contain only whitespace. These states look similar in the designer but behave differently in comparisons, validation, and most Text blocks. + +| State | Example | Length | Notes | +| --- | --- | --- | --- | +| `null` | Unassigned [String][], or explicitly `null` | — | No string instance. See [Null and Nullable Types][]. | +| Empty text | `""` or [String.Empty][] | `0` | A valid string with no characters. | +| Whitespace-only | `" "`, `"\t"`, `"\r\n"` | Greater than `0` | Not empty; every character is space, tab, CR, or LF (Is Text blocks). | +| Non-empty text | `"Cortex"`, `" a "` | Greater than `0` | Contains at least one non-whitespace character (the last example also contains spaces). | + +| Goal | Prefer | +| --- | --- | +| A defined empty value | `""` or [String.Empty][] — not `null` | +| Test for `null` only | [Is Text Null][] or `($)Text == null` | +| Test for empty only (`""`) | [Is Text Empty][] or `($)Text == ""` / `($)Text.Length == 0` | +| Test for `null` or empty | [Is Text Null Or Empty][] or [String.IsNullOrEmpty][] | +| Test for empty or whitespace-only (space, tab, CR, LF) | [Is Text Empty Or Whitespace][] | +| Test for `null`, empty, or whitespace-only (space, tab, CR, LF) | [Is Text Null, Empty Or Whitespace][] | +| Test for `null`, empty, or whitespace-only (.NET set) | [String.IsNullOrWhiteSpace][] | -- What is an Empty Text? -- What is Whitespace? -- Difference between empty text and whitespace +Treat `null`, empty, and whitespace as distinct cases when validating input. For an overview of text in {{% ctx %}}, see [What is Text?][]. ## Empty Text -TODO: +**Empty text** is a [String][] whose length is `0`. It contains no characters. It is **not** the same as `null`: empty text is a real string instance. + +### Creating empty text + +In the [Expression Editor][], create empty text with either form: + +| Expression | Meaning | +| --- | --- | +| `""` | Empty string literal | +| `String.Empty` | The [String.Empty][] field (same value as `""`) | + +Both evaluate to the same empty string. Prefer one style consistently in a flow. + +```csharp +($)Message = "" +($)Message = String.Empty +``` + +In the [Literal Editor][], surrounding quotes are not required for [String][] properties; an empty literal value is empty text. Leaving some properties with **no value** (for example on [Set Variable][]) can set the variable to `null` instead — see [Empty versus no value][]. -- How to create an empty text - `""` or `String.Empty` +### Properties of empty text + +| Check | Text | Result | +| --- | --- | --- | +| `($)Text == null` | `""` | `false` | +| `($)Text == ""` or `($)Text == String.Empty` | `""` | `true` | +| `($)Text.Length` | `""` | `0` | +| [Is Text Empty][] | `""` | `true` | +| [Is Text Empty][] | `" "` | `false` (whitespace is not empty) | +| [Is Text Empty][] | `null` | `false` (`null` is not empty) | ## Whitespace -https://learn.microsoft.com/en-us/dotnet/api/system.char.iswhitespace?view=net-5.0#System_Char_IsWhiteSpace_System_Char_ and textual representation \t \r \n etc. +In {{% ctx %}} Text blocks that check for whitespace (for example [Is Text Empty Or Whitespace][] and [Is Text Null, Empty Or Whitespace][]), **whitespace** means any of these characters: + +| Character | Escape / literal | Unicode | Notes | +| --- | --- | --- | --- | +| Space | `" "` | U+0020 | Ordinary space | +| Character tabulation (tab) | `"\t"` | U+0009 | Horizontal tab | +| Line feed (newline) | `"\n"` | U+000A | Unix-style line ending component | +| Carriage return | `"\r"` | U+000D | Often combined with `\n` as `"\r\n"` | + +Escape sequences apply in regular (non-verbatim) string literals in the [Expression Editor][] — see [String escape sequences][]. + +A string is **whitespace-only** when its length is greater than `0` and every character is one of the whitespace characters above. Whitespace-only text is **not** empty: `" ".Length` is `3`, not `0`. + +### .NET whitespace (expressions) + +In expressions, [Char.IsWhiteSpace][] and [String.IsNullOrWhiteSpace][] use a **broader** Unicode set than the Is Text whitespace blocks. That set includes the four characters above plus others such as no-break space (U+00A0), vertical tab (U+000B), form feed (U+000C), next line (U+0085), line separator (U+2028), paragraph separator (U+2029), and further space-separator code points. See [Char.IsWhiteSpace][]. + +| Approach | Whitespace characters | +| --- | --- | +| [Is Text Empty Or Whitespace][], [Is Text Null, Empty Or Whitespace][] | Space, tab, carriage return, line feed only | +| [String.IsNullOrWhiteSpace][], [Char.IsWhiteSpace][] | Full .NET whitespace set | + +Use the Is Text blocks when you want the product definition (the four characters). Use [String.IsNullOrWhiteSpace][] or [Char.IsWhiteSpace][] in the [Expression Editor][] only when you need the broader .NET behaviour. + +### Examples + +| Text | Empty? | Whitespace-only? (blocks) | Notes | +| --- | --- | --- | --- | +| `""` | Yes | No | Length `0` | +| `" "` | No | Yes | Spaces only | +| `"\t\r\n"` | No | Yes | Tab and line endings only | +| `" a "` | No | No | Contains a non-whitespace character | +| `"\u00A0"` (no-break space) | No | No | Not whitespace for Is Text blocks; is whitespace for [Char.IsWhiteSpace][] | +| `null` | No | No | Not a string instance | + +## Checking null, empty, and whitespace + +### Using blocks + +Use the Is Text blocks when you want a clear yes/no result in a flow: + +| Block | Returns `true` when Text is… | +| --- | --- | +| [Is Text Null][] | `null` | +| [Is Text Empty][] | empty (`""`) | +| [Is Text Null Or Empty][] | `null` or empty | +| [Is Text Empty Or Whitespace][] | empty or whitespace-only (space, tab, CR, LF) | +| [Is Text Null, Empty Or Whitespace][] | `null`, empty, or whitespace-only (space, tab, CR, LF) | + +Each block's remarks document how `null` and whitespace-only values are treated. For example, [Is Text Empty][] returns `false` for both `null` and `" "`. + +### Using expressions + +| Need | Example | +| --- | --- | +| Is `null`? | `($)Text == null` | +| Is empty? | `($)Text == ""` or `($)Text == String.Empty` | +| Is `null` or empty? | `string.IsNullOrEmpty(($)Text)` | +| Is `null`, empty, or whitespace-only (.NET set)? | `string.IsNullOrWhiteSpace(($)Text)` | +| Character is whitespace (.NET set)? | `char.IsWhiteSpace(($)Text[0])` | + +[String.IsNullOrEmpty][] matches [Is Text Null Or Empty][]. [String.IsNullOrWhiteSpace][] is **not** an exact equivalent of [Is Text Null, Empty Or Whitespace][] because .NET recognizes additional whitespace characters — see [Whitespace characters in Is Text blocks][]. Prefer blocks when the decision should be visible on the flow canvas and you want the product whitespace definition; use expressions inside other property values or more complex conditions. ## Remarks -### Known Limitations +### Null versus empty text + +`null` means there is no [String][] instance. Empty text (`""` / [String.Empty][]) is an instance with length `0`. Equality checks, length, and most string methods behave differently for the two. See [Null and Nullable Types][] and [Is Text Null][]. + +### Empty versus whitespace-only text + +Whitespace-only text has length greater than `0`, so it is not empty. Blocks and APIs that test only for empty (for example [Is Text Empty][] or [String.IsNullOrEmpty][]) return `false` for `" "`. Use [Is Text Empty Or Whitespace][] or [Is Text Null, Empty Or Whitespace][] when whitespace-only input (space, tab, CR, or LF) should be treated like missing content. + +### Empty versus no value + +Leaving a property with **no value** is not the same as empty text. On [Set Variable][], leaving Value unset typically assigns `null`. To assign empty text deliberately, use `""` or `String.Empty` in the [Expression Editor][] (or an explicit empty string in the editor that applies). See [Null and Nullable Types][]. + +### Prefer defined empty values + +When a flow needs a defined empty string (for concatenation, default messages, or APIs that reject `null`), assign `""` or [String.Empty][] rather than leaving the value unset. Reserve `null` for “no value provided.” + +### Whitespace characters in Is Text blocks -TODO +[Is Text Empty Or Whitespace][] and [Is Text Null, Empty Or Whitespace][] treat only space, tab, carriage return, and line feed as whitespace. Other Unicode whitespace characters (for example no-break space U+00A0) are **not** treated as whitespace by those blocks. [String.IsNullOrWhiteSpace][] and [Char.IsWhiteSpace][] in expressions use the broader .NET set — see [.NET whitespace (expressions)][]. ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings, immutability, and overview of text concepts +* [Null and Nullable Types][] — `null` for reference and nullable types +* [Equality][] — comparing text with [StringComparison][] +* [Casing][] — changing case (not the same as trimming whitespace) ### Related Data Types -TODO +* [String][] +* [Char][] ### Related Blocks -TODO +* [Is Text Null][] +* [Is Text Empty][] +* [Is Text Null Or Empty][] +* [Is Text Empty Or Whitespace][] +* [Is Text Null, Empty Or Whitespace][] ### External Documentation -TODO +* [Char.IsWhiteSpace][] +* [String.Empty][] +* [String.IsNullOrEmpty][] +* [String.IsNullOrWhiteSpace][] +* [String escape sequences][] +* [System.String][] +* [System.Char][] + +[Empty versus no value]: {{< ref "#empty-versus-no-value" >}} +[Whitespace characters in Is Text blocks]: {{< ref "#whitespace" >}} +[.NET whitespace (expressions)]: {{< ref "#net-whitespace-expressions" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Null and Nullable Types]: {{< url path="Cortex.Reference.Concepts.Fundamentals.DataTypes.NullAndNullableTypes.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} +[Casing]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Casing.MainDoc" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[Char]: {{< url path="Cortex.Reference.DataTypes.Text.Char.MainDoc" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[Literal Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.LiteralEditor.MainDoc" >}} + +[Set Variable]: {{< url path="Cortex.Reference.Blocks.Variables.SetVariable.SetVariable.MainDoc" >}} + +[Is Text Null]: {{< url path="Cortex.Reference.Blocks.Text.IsText.IsTextNull.MainDoc" >}} +[Is Text Empty]: {{< url path="Cortex.Reference.Blocks.Text.IsText.IsTextEmpty.MainDoc" >}} +[Is Text Null Or Empty]: {{< url path="Cortex.Reference.Blocks.Text.IsText.IsTextNullOrEmpty.MainDoc" >}} +[Is Text Empty Or Whitespace]: {{< url path="Cortex.Reference.Blocks.Text.IsText.IsTextEmptyOrWhitespace.MainDoc" >}} +[Is Text Null, Empty Or Whitespace]: {{< url path="Cortex.Reference.Blocks.Text.IsText.IsTextNullEmptyOrWhitespace.MainDoc" >}} + +[Char.IsWhiteSpace]: {{< url path="MSDocs.DotNet.Api.System.Char.IsWhiteSpace" >}} +[String.Empty]: {{< url path="MSDocs.DotNet.Api.System.String.Empty" >}} +[String.IsNullOrEmpty]: {{< url path="MSDocs.DotNet.Api.System.String.IsNullOrEmpty" >}} +[String.IsNullOrWhiteSpace]: {{< url path="MSDocs.DotNet.Api.System.String.IsNullOrWhiteSpace" >}} +[String escape sequences]: {{< url path="MSDocs.CSharp.EscapeSequences" >}} +[System.String]: {{< url path="MSDocs.DotNet.Api.System.String.MainDoc" >}} +[System.Char]: {{< url path="MSDocs.DotNet.Api.System.Char.MainDoc" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/encoding.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/encoding.md index 0c7b295a5..9870facb4 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/encoding.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/encoding.md @@ -1,49 +1,222 @@ --- title: "Encoding" linkTitle: "Encoding" -description: "Information regarding text encoding." +description: "How text encoding works in CORTEX: character encodings for files and bytes (System.Text.Encoding), and TextEncodingFormat values used by Encode Text and Decode Text." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +In {{% ctx %}}, **encoding** has two related meanings: + +| Topic | What it does | Typical use | +| --- | --- | --- | +| [Character encodings][] | Maps a [String][] to and from a sequence of bytes (`System.Text.Encoding`) | Reading, writing, and searching files | +| [Text encode/decode formats][] | Transforms text to another text representation (`TextEncodingFormat`) | [Encode Text][] / [Decode Text][] with a [TextEncodingFormat][] value | + +Character encoding is about how characters become bytes on disk or the network. Text encode/decode formats rewrite a string into another string form (for example Base64 or Html)—they are not the same as choosing UTF-8 versus UTF-16 for a file. + +| Goal | Prefer | +| --- | --- | +| Read or write a text file with a known character encoding | File blocks with an explicit [Encoding][Encoding data type] (for example `Encoding.UTF8`) | +| Let file read/search detect encoding from a BOM | Leave **Encoding** as `null` on read/search blocks — see [What are Files and Folders?][] | +| Write UTF-8 without a BOM | Leave **Encoding** as `null` on write blocks | +| Convert text with a [TextEncodingFormat][] (Base64, Url, Hex, Html, Utf8, Base64Url) | [Encode Text][] | +| Reverse an encode/decode format | [Decode Text][] with the matching [TextEncodingFormat][] | + +For string basics and immutability, see [What is Text?][]. For .NET background on character encodings, see [Introduction to character encoding in .NET][]. + +## Character encodings + +In .NET, a [String][] is stored as UTF-16 Unicode characters. An [Encoding][Encoding data type] (`System.Text.Encoding`) converts between that in-memory string and a byte sequence—for example when reading or writing a file. Different encodings use different code pages and byte layouts; choosing the wrong encoding garbles text that falls outside the encoding's repertoire. See [Introduction to character encoding in .NET][] and [System.Text.Encoding][MS Encoding]. + +### Common encodings + +| Name | Typical expression | Notes | +| --- | --- | --- | +| UTF-8 | `Encoding.UTF8` | Variable-length Unicode; widely used for files and APIs. See [UTF8Encoding][]. Provides a UTF-8 byte order mark (BOM) preamble—see [UTF-8 and byte order marks][]. | +| Unicode (UTF-16 LE) | `Encoding.Unicode` | Little-endian UTF-16. See [UnicodeEncoding][]. | +| Big-endian Unicode | `Encoding.BigEndianUnicode` | Big-endian UTF-16 (`UnicodeEncoding` with big-endian byte order). | +| UTF-32 | `Encoding.UTF32` or `new UTF32Encoding()` | Fixed-width 32-bit Unicode. See [UTF32Encoding][]. | +| ASCII | `Encoding.ASCII` or `new ASCIIEncoding()` | 7-bit ASCII (code page 20127); values above 127 are not representable as ASCII. | +| Named or code-page encoding | `Encoding.GetEncoding("utf-8")`, `Encoding.GetEncoding(20127)`, `Encoding.GetEncoding("iso-8859-1")` | Resolves an encoding by name or Windows code page number. | + +`Encoding.Default` is the OS ANSI code page—avoid it for portable flows because it depends on the execution server. Prefer an explicit encoding such as `Encoding.UTF8` when behaviour must be the same on every server. + +### Specifying Encoding in the Expression Editor + +On file blocks, set **Encoding** in the [Expression Editor][] with any of these forms: + +| Need | Example | +| --- | --- | +| UTF-8 (with BOM preamble) | `Encoding.UTF8` | +| UTF-8 without BOM | `new UTF8Encoding(false)` | +| ASCII | `Encoding.ASCII` | +| Code page by number | `Encoding.GetEncoding(20127)` | +| Encoding by name | `Encoding.GetEncoding("utf-8")` | +| Construct ASCII encoding type | `new ASCIIEncoding()` | + +`Encoding.UTF8` and `new UTF8Encoding()` are **not** interchangeable: the parameterless `new UTF8Encoding()` constructor defaults to **no** BOM, while `Encoding.UTF8` provides a BOM preamble. Use `new UTF8Encoding(false)` or `new UTF8Encoding(true)` when you need that choice to be explicit. See [UTF-8 and byte order marks][]. + +An invalid encoding expression (for example `Encoding.GetEncoding(-1)`) causes file blocks to throw [InvalidPropertyValueException][]. Leave **Encoding** unset (`null`) when you want the block's default null behaviour described below. + +### Encoding on file blocks + +File read, write, and search blocks expose an optional **Encoding** property: + +| Situation | Behaviour | +| --- | --- | +| Read or search, **Encoding** is `null` | The block attempts to detect encoding from byte order marks (BOM) when possible | +| Write, **Encoding** is `null` | Text is written as UTF-8 **without** a BOM | +| **Encoding** set explicitly | That encoding is used for the operation | + +See [What are Files and Folders?][] and the individual blocks under [Related Blocks][]. + +### UTF-8 and byte order marks -- Available encodings -- How to define them/use them - - Different ways to specify encoding: - - Encoding encoding = Encoding.GetEncoding(20127); - - Encoding encoding = new ASCIIEncoding(); - - Encoding encoding = Encoding.ASCII; -- Talk about encode/decode text blocks including base64/url/hex/html/utf8/base64url +A BOM is an optional signature at the start of a file that identifies the encoding. For UTF-8 writes in {{% ctx %}}: -Links: +| Expression / setting | Typical BOM behaviour | +| --- | --- | +| **Encoding** left as `null` on write blocks | UTF-8 **without** a BOM | +| `Encoding.UTF8` | UTF-8 **with** a BOM preamble | +| `new UTF8Encoding(false)` / parameterless `new UTF8Encoding()` | UTF-8 **without** a BOM | +| `new UTF8Encoding(true)` | UTF-8 **with** a BOM | -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding-introduction +Choose `null` or `new UTF8Encoding(false)` when consumers expect UTF-8 without a leading BOM. Use `Encoding.UTF8` or `new UTF8Encoding(true)` when a BOM is required. + +## Text encode and decode formats + +[Encode Text][] and [Decode Text][] transform a [String][] using a [TextEncodingFormat][] value. These formats produce another string (encoded or decoded text)—they are not the same as choosing a `System.Text.Encoding` (such as `Encoding.UTF8`) for file I/O. + +| [TextEncodingFormat][] | Meaning | +| --- | --- | +| [Base64][] | Base64 encoding format | +| [Url][] | Url encoding format | +| [Hex][] | Hex (hexadecimal) encoding format | +| [Html][] | Html encoding format | +| [Utf8][] | Utf8 encoding format — a [TextEncodingFormat][] value used by encode/decode text blocks, **not** the character encoding `Encoding.UTF8` used for files | +| [Base64Url][] | Base64Url encoding format | + +All of these values are defined on [TextEncodingFormat][]. [Decode Text][] documents each of them as a supported **Format**. [Encode Text][] examples cover Base64, Url, Hex, and Html; the same [TextEncodingFormat][] enum supplies Utf8 and Base64Url as well. + +When encoding to [Base64][], a newline is inserted every 76 characters—see [Known Limitations][]. ## Remarks +### Round-tripping + +You can pass text produced by [Encode Text][] into [Decode Text][] (same [TextEncodingFormat][]), and pass decoded text back through [Encode Text][], to round-trip the value. Prefer the matching format on both blocks. For format-specific edge cases, see the Remarks on [Decode Text][]. + +### Immutable strings + +Encode and decode do not edit a [String][] in place. Blocks produce a new string and assign it to the **Text** variable. See [Immutability of strings][]. + +### Decoding Url out-of-range sequences + +When decoding [Url][], percent-sequences outside the valid range (`%00`–`%ff`) are treated as literal characters (for example `"%zzExample%21"` becomes `"%zzExample!"`). See [Decode Text][]. + +### Decoding Hex overflow + +When decoding [Hex][], characters outside `0-9` and `A-F` overflow rather than failing (for example `G` overflows toward `0`). See [Decode Text][] for examples. + +### Decoding Html entities, ampersands, and semicolons + +When decoding [Html][]: + +* Invalid [HTML entities][HTMLEntity] are removed +* An ampersand that is not part of an entity is removed +* A semicolon that is not part of an entity is kept as a literal character + +Full examples are on [Decode Text][]. + +### TextDecodingException + +Invalid Base64, Hex (odd number of characters), or Base64Url input can throw [TextDecodingException][] when using [Decode Text][]. See [TextDecodingException][] for error codes. + ### Known Limitations -TODO +* When encoding [Base64][], a newline is inserted every 76 characters (see [Encode Text][]). +* When decoding [Html][], HTML5 named [entities][HTMLEntity] (for example `"φ"`) are removed (see [Decode Text][]). ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings, immutability, and overview of text concepts +* [What are Files and Folders?][] — file operations and default encoding behaviour +* [Immutability of strings][] ### Related Data Types -TODO +* [Encoding][Encoding data type] +* [UTF8Encoding][] +* [UnicodeEncoding][] +* [UTF32Encoding][] +* [TextEncodingFormat][] +* [String][] +* [TextDecodingErrorCode][] ### Related Blocks -TODO +* [Encode Text][] +* [Decode Text][] +* [Read All Text][] +* [Read All Lines][] +* [Write All Text][] +* [Write All Lines][] +* [Search File][] +* [Search Files][] ### External Documentation -TODO +* [Introduction to character encoding in .NET][] +* [System.Text.Encoding][MS Encoding] +* [System.Text.UTF8Encoding][MS UTF8Encoding] +* [System.Text.UnicodeEncoding][MS UnicodeEncoding] +* [System.Text.UTF32Encoding][MS UTF32Encoding] + +[Character encodings]: {{< ref "#character-encodings" >}} +[Text encode/decode formats]: {{< ref "#text-encode-and-decode-formats" >}} +[Related Blocks]: {{< ref "#related-blocks" >}} +[Known Limitations]: {{< ref "#known-limitations" >}} +[UTF-8 and byte order marks]: {{< ref "#utf-8-and-byte-order-marks" >}} +[Immutability of strings]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.ImmutabilityOfStrings" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[What are Files and Folders?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.FilesAndFolders.WhatAreFilesAndFolders.MainDoc" >}} + +[Encoding data type]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.MainDoc" >}} +[UTF8Encoding]: {{< url path="Cortex.Reference.DataTypes.Text.UTF8Encoding.MainDoc" >}} +[UnicodeEncoding]: {{< url path="Cortex.Reference.DataTypes.Text.UnicodeEncoding.MainDoc" >}} +[UTF32Encoding]: {{< url path="Cortex.Reference.DataTypes.Text.UTF32Encoding.MainDoc" >}} +[TextEncodingFormat]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.MainDoc" >}} +[Base64]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Base64" >}} +[Url]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Url" >}} +[Hex]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Hex" >}} +[Html]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Html" >}} +[Utf8]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Utf8" >}} +[Base64Url]: {{< url path="Cortex.Reference.DataTypes.Text.Encoding.TextEncodingFormat.Base64Url" >}} +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[TextDecodingErrorCode]: {{< url path="Cortex.Reference.DataTypes.Text.TextDecodingErrorCode.MainDoc" >}} + +[Encode Text]: {{< url path="Cortex.Reference.Blocks.Text.EncodeText.EncodeText.MainDoc" >}} +[Decode Text]: {{< url path="Cortex.Reference.Blocks.Text.DecodeText.DecodeText.MainDoc" >}} +[Read All Text]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.ReadFile.ReadAllText.MainDoc" >}} +[Read All Lines]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.ReadFile.ReadAllLines.MainDoc" >}} +[Write All Text]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.WriteFile.WriteAllText.MainDoc" >}} +[Write All Lines]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.WriteFile.WriteAllLines.MainDoc" >}} +[Search File]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFile.MainDoc" >}} +[Search Files]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFiles.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[InvalidPropertyValueException]: {{< url path="Cortex.Reference.Exceptions.Flows.Blocks.InvalidPropertyValueException.MainDoc" >}} +[TextDecodingException]: {{< url path="Cortex.Reference.Exceptions.Text.Encoding.TextDecodingException.MainDoc" >}} +[HTMLEntity]: {{< url path="Cortex.Reference.Glossary.F-J.HTMLEntity" >}} + +[Introduction to character encoding in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.CharacterEncodingIntroduction" >}} +[MS Encoding]: {{< url path="MSDocs.DotNet.Api.System.Text.Encoding" >}} +[MS UTF8Encoding]: {{< url path="MSDocs.DotNet.Api.System.Text.Utf8Encoding" >}} +[MS UnicodeEncoding]: {{< url path="MSDocs.DotNet.Api.System.Text.UnicodeEncoding" >}} +[MS UTF32Encoding]: {{< url path="MSDocs.DotNet.Api.System.Text.Utf32Encoding" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/equality.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/equality.md index 51142f784..1f4965d67 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/equality.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/equality.md @@ -1,126 +1,327 @@ --- title: "Equality" linkTitle: "Equality" -description: "Information regarding text equality." +description: "How text equality works in CORTEX: StringComparison rules, when to use ordinal vs culture-sensitive comparison, best practices, and worked examples." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +**Text equality** is the rule used to decide whether two pieces of text match. In {{% ctx %}}, that rule is controlled by a [StringComparison][] value (the **Comparison Type** property on text blocks, or a `StringComparison` argument in expressions). The same two strings can match or not match depending on whether comparison is ordinal or culture-sensitive, and whether case is considered. + +This is separate from [object equality][Object Equality], which covers value vs reference equality for objects in general. [String][] values can still use `==` / `Equals` in the [Expression Editor][]; when you need culture or case rules made explicit—especially in text blocks—use [StringComparison][] as described on this page. + +### Best practices -- Best Practices - - Use Ordinal to remove ambiguity and increase performance - - Comparing strings - do not lower or upper pick a relevant culture or ignore case +| Goal | Prefer | +| --- | --- | +| Exact, culture-independent matching (identifiers, protocols, security checks, most internal text) | [Ordinal][] or [Ordinal Ignore Case][] | +| Case-insensitive matching without culture rules | [Ordinal Ignore Case][] | +| Linguistic matching that must stay the same on every server | [Invariant Culture][] or [Invariant Culture Ignore Case][] | +| Linguistic matching that follows the execution server's locale | [Current Culture][] or [Current Culture Ignore Case][] | -Links: +Follow these practices when comparing text in flows: -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case#compare-strings-of-mixed-case +* Prefer [Ordinal][] or [Ordinal Ignore Case][] as the safe default unless you need culture-specific linguistic rules. Ordinal comparison removes ambiguity and is typically faster. +* Do **not** call `ToLower` / `ToUpper` (or casing blocks) only to decide whether two strings match. Use an ignore-case [StringComparison][] value instead—see [Casing][] and [Compare strings of mixed case][]. +* Specify the comparison type explicitly on blocks and in expressions so intent is clear and behaviour does not depend on implicit defaults. + +Most text blocks that search or match text (for example [Contains Text][]) default **Comparison Type** to `Ordinal`. See [Best practices for comparing strings in .NET][]. ## Comparison Types -Comparison Types specify the rules used to determine whether two pieces of text match. +Comparison types specify the rules used to determine whether two pieces of text match. They correspond to the [StringComparison][] enumeration values. The table below lists the supported comparison types: -| Name | Text Value | Numeric Value | Description | +| Name | Text Value | Numeric Value | Description | |----------|------------|---------------|-------------| -| [Ordinal][] | StringComparison.Ordinal | 4 | Used to compare text using ordinal sort rules. Case of the texts is considered when comparing. | -| [Ordinal Ignore Case][] | StringComparison.OrdinalIgnoreCase | 5 | Used to compare text using ordinal sort rules. Case of the texts is ignored when comparing. | -| [Invariant Culture][] | StringComparison.InvariantCulture | 2 | Used to compare text using culture-sensitive sort rules and the invariant culture. Case of the texts is considered when comparing. | -| [Invariant Culture Ignore Case][] | StringComparison.InvariantCultureIgnoreCase | 3 | Used to compare text using culture-sensitive sort rules and the invariant culture. Case of the texts is ignored when comparing. | -| [Current Culture][] | StringComparison.CurrentCulture | 0 | Used to compare text using culture-sensitive sort rules and the current culture. Case of the texts is considered when comparing. | -| [Current Culture Ignore Case][] | StringComparison.CurrentCultureIgnoreCase | 1 | Used to compare text using culture-sensitive sort rules and the current culture. Case of the texts is ignored when comparing. | - -For more information about comparison types, please see [StringComparison][]. +| [Ordinal][] | `StringComparison.Ordinal` | 4 | Compares text using ordinal (binary) sort rules. Case is considered. | +| [Ordinal Ignore Case][] | `StringComparison.OrdinalIgnoreCase` | 5 | Compares text using ordinal (binary) sort rules. Case is ignored. | +| [Invariant Culture][] | `StringComparison.InvariantCulture` | 2 | Compares text using culture-sensitive sort rules and the [invariant culture][Invariant Culture concept]. Case is considered. | +| [Invariant Culture Ignore Case][] | `StringComparison.InvariantCultureIgnoreCase` | 3 | Compares text using culture-sensitive sort rules and the [invariant culture][Invariant Culture concept]. Case is ignored. | +| [Current Culture][] | `StringComparison.CurrentCulture` | 0 | Compares text using culture-sensitive sort rules and the [current culture][Current Culture concept]. Case is considered. | +| [Current Culture Ignore Case][] | `StringComparison.CurrentCultureIgnoreCase` | 1 | Compares text using culture-sensitive sort rules and the [current culture][Current Culture concept]. Case is ignored. | -TODO: Consider moving sections below into the StringComparison Data Type documentation and removed from this page +For the data type itself (enum values, casting, property editors), see [StringComparison][]. For how each rule behaves when matching text, see the sections below. ### Ordinal -TODO: +[Ordinal][] comparison (`StringComparison.Ordinal`) compares text by Unicode code point (binary) values. It does **not** apply linguistic or culture-specific equivalence rules. Case is considered: `"windows"` does not match `"Windows"`. + +#### When to use + +* Default choice when you are unsure which comparison type to pick +* Identifiers, keys, protocols, file paths, XML/HTML tags, security checks, and other culture-independent text +* Exact matching where linguistically equivalent characters (for example `æ` and `ae`) must **not** be treated as equal +* Performance-sensitive matching + +#### When not to use + +* User-facing linguistic equality where culture rules should treat related characters as equivalent—use [Invariant Culture][] or [Current Culture][] instead +* Case-insensitive matching—use [Ordinal Ignore Case][] instead -- When to use? If not sure what to choose? -- Ordinal sort rules -- notes about ordinal and a worked through example +#### Example + +| Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | +| `"dog"` | `"dog"` | Yes | Identical code points | +| `"dog"` | `"Dog"` | No | Case differs | +| `"resume"` | `"résumé"` | No | Accented characters differ | +| `"encyclopædia"` | `"encyclopaedia"` | No | No expansion of `æ` to `ae` | +| `"é"` (U+00E9) | `"e\u0301"` (`e` + combining acute) | No | Different code-point sequences; see [Unicode representations][] | + +Ordinal comparison is never affected by the server's [current culture][Current Culture concept]. ### Ordinal Ignore Case -TODO: +[Ordinal Ignore Case][] (`StringComparison.OrdinalIgnoreCase`) uses the same ordinal (binary) rules as [Ordinal][], except that case differences are ignored. Under this comparison, `'d'` matches `'D'` and `'á'` matches `'Á'`, but unaccented `'a'` still does not match accented `'á'`. + +#### When to use + +* Case-insensitive matching for identifiers, protocols, or other culture-independent text +* Safe default when case must not matter and linguistic culture rules are not required + +#### When not to use + +* Case-sensitive exact matching—use [Ordinal][] +* Linguistic case folding that follows a locale (for example Turkish `i` / `İ`)—use [Current Culture Ignore Case][] or [Invariant Culture Ignore Case][] as appropriate + +#### Example + +| Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | +| `"dog"` | `"dog"` | Yes | Identical | +| `"dog"` | `"Dog"` | Yes | Case ignored | +| `"FILE:"` | `"file:"` | Yes | Typical protocol / URI prefix check | +| `"resume"` | `"résumé"` | No | Accents still differ | -- When to use? If not sure what to choose? -- Link to Ordinal sort rules, only difference is that it doesn't consider casing when comparing characters -- notes about ordinal ignore case and a worked through example +For mixed-case equality without changing case first, prefer this value (or another ignore-case comparison type) over `ToLower` / `ToUpper`. See [Compare strings of mixed case][]. ### Invariant Culture -TODO: +[Invariant Culture][] comparison (`StringComparison.InvariantCulture`) uses culture-sensitive (linguistic) sort and equivalence rules from the [invariant culture][Invariant Culture concept]. Case is considered. Results are consistent across servers and do not follow the OS regional settings of the execution server. -- When to use? If not sure what to choose? -- Link to Culture -> Invariant Culture -- Invariant Culture rules -- notes about invariant culture and a worked through example +Ordinal is still preferred when the comparison is **not** linguistically meaningful (symbolic identifiers, protocols, and most internal keys). Use invariant culture when you need linguistic rules without tying behaviour to [Current Culture][Current Culture concept]. + +#### When to use + +* Linguistically meaningful text that must compare the same on every server +* Culture-independent persistence or exchange where character equivalences matter but server locale must not + +#### When not to use + +* Symbolic or non-linguistic matching—prefer [Ordinal][] +* User-facing comparisons that should follow the server or a specific locale—use [Current Culture][] or a known specific culture's rules via current-culture settings on the server + +#### Example + +| Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | +| `"case"` | `"Case"` | No | Case differs | +| `"é"` (U+00E9) | `"e\u0301"` (`e` + combining acute) | Yes | Linguistic / normalization-aware equivalence under invariant culture | +| `"encyclopædia"` | `"encyclopaedia"` | No | Under modern .NET (ICU), `æ` is **not** expanded to `ae` | +| `"Å"` (U+00C5) | `"A\u030A"` (`A` + combining ring) | Yes | Precomposed versus combining form | + +For more on the invariant culture, see [Invariant Culture][Invariant Culture concept]. Ordinal comparison treats the precomposed and combining forms above as **different**—see [Ordinal][]. ### Invariant Culture Ignore Case -TODO: +[Invariant Culture Ignore Case][] (`StringComparison.InvariantCultureIgnoreCase`) uses the same invariant linguistic rules as [Invariant Culture][], except that case is ignored. + +#### When to use + +* Case-insensitive linguistic matching that must be identical on every server +* Persisted or internal text where culture-sensitive equivalence matters but locale must not -- When to use? If not sure what to choose? -- Link to Culture -> Invariant Culture -- Link to Invariant Culture sort rules, only difference is that it doesn't consider casing when comparing characters -- notes about invariant culture ignore case and a worked through example +#### When not to use + +* Case-insensitive symbolic matching—prefer [Ordinal Ignore Case][] +* Case-insensitive matching that should follow the server locale—use [Current Culture Ignore Case][] + +#### Example + +| Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | +| `"case"` | `"Case"` | Yes | Case ignored | +| `"Archæology"` | `"ARCHÆOLOGY"` | Yes | Case ignored; same `æ` character in both | +| `"encyclopædia"` | `"encyclopaedia"` | No | Under modern .NET (ICU), `æ` is **not** expanded to `ae` | +| `"é"` (U+00E9) | `"E\u0301"` | Yes | Case ignored; precomposed versus combining form | ### Current Culture -TODO: +[Current Culture][] comparison (`StringComparison.CurrentCulture`) uses culture-sensitive sort and equivalence rules from the [current culture][Current Culture concept] of the execution server (`CultureInfo.CurrentCulture`). Case is considered. + +#### When to use + +* User-facing or locale-aware matching that should follow the **server** regional settings +* Linguistic equivalences defined by that locale (which can differ between cultures) + +#### When not to use + +* Security checks, stored identifiers, or protocols—use [Ordinal][] or [Invariant Culture][] +* Behaviour that must be identical on every server—use [Ordinal][] or [Invariant Culture][] +* Gateway users may use a different locale than the execution server + +In a **cluster**, keep operating system culture and regional settings aligned on every node that runs the flow. See [Current Culture][Current Culture concept]. -- When to use? If not sure what to choose? -- Link to Culture -> Current Culture -- Current Culture rules -- notes about current culture and a worked through example +#### Example + +Results depend on the server's current culture. Illustrative cases under modern .NET (ICU): + +| Current culture | Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | --- | +| `th-TH` | `"a"` | `"a-"` | Yes | Thai rules treat some punctuation as ignorable | +| `en-US` | `"a"` | `"a-"` | No | Hyphen is significant under en-US rules | +| Any | `"é"` (U+00E9) | `"e\u0301"` | Yes | Precomposed versus combining acute | +| Any | `"case"` | `"Case"` | No | Case is considered | +| Any | `"encyclopædia"` | `"encyclopaedia"` | No | Under ICU, `æ` is not expanded to `ae` | ### Current Culture Ignore Case -TODO: +[Current Culture Ignore Case][] (`StringComparison.CurrentCultureIgnoreCase`) uses the same current-culture linguistic rules as [Current Culture][], except that case is ignored according to that culture's casing conventions. + +#### When to use + +* Case-insensitive, locale-aware matching for user-facing text on a server whose culture matches the audience + +#### When not to use -- When to use? If not sure what to choose? -- Link to Culture -> Current Culture -- Link to Current Culture sort rules, only difference is that it doesn't consider casing when comparing characters -- notes about current culture ignore case and a worked through example +* Security-sensitive or culture-independent matching—use [Ordinal Ignore Case][] (or [Invariant Culture Ignore Case][] when linguistic rules are required without locale dependence) +* Case folding that must not change when regional settings change + +#### Example + +| Current culture | Text 1 | Text 2 | Match? | Notes | +| --- | --- | --- | --- | --- | +| `en-US` | `"case"` | `"Case"` | Yes | Case ignored | +| `tr-TR` | `"i"` | `"İ"` | Yes | Turkish dotted/dotless *I* rules | +| `en-US` | `"i"` | `"İ"` | No | English case folding does not equate these | +| `en-US` | `"Archæology"` | `"ARCHÆOLOGY"` | Yes | Case ignored under current culture | + +Turkish casing is a common source of bugs when culture-sensitive ignore-case comparison is used for protocols or identifiers. Prefer [Ordinal Ignore Case][] for those scenarios. See [Casing][] and [Current Culture][Current Culture concept]. ## Remarks +### Comparing text versus changing case + +Do **not** convert both sides of a comparison to lower or upper case to ignore case. That approach is culture-sensitive unless you always use a fixed culture, allocates extra strings, and is easy to get wrong across locales (for example Turkish `i` / `İ`). + +Instead: + +* For equality or search of mixed-case text, use a [StringComparison][] ignore-case value such as `OrdinalIgnoreCase`, `InvariantCultureIgnoreCase`, or `CurrentCultureIgnoreCase` +* Use casing blocks or `ToUpper` / `ToLower` only when you need the **cased text** as output or stored data + +See [Casing][] and [Compare strings of mixed case][]. + +### Ordinal versus linguistic matching + +[Ordinal][] and [Ordinal Ignore Case][] compare code points. Culture-sensitive types ([Invariant Culture][], [Current Culture][], and their ignore-case variants) can treat some character sequences as equivalent even when the code points differ—for example precomposed `é` (U+00E9) versus `e` plus a combining acute, or (under Thai) `"a"` versus `"a-"`. + +Choose ordinal rules when characters must match exactly; choose linguistic rules when natural-language equivalence is required. Microsoft recommends ordinal comparison when the comparison is linguistically irrelevant. See [Best practices for comparing strings in .NET][]. + +On modern .NET runtimes that use ICU globalization, some older NLS expansions (for example treating `æ` as equal to `ae`) no longer apply. Prefer examples and tests against the runtime your {{% ctx %}} environment uses. + +### Unicode representations + +The same visible character can be stored as different Unicode sequences (for example precomposed `é` versus `e` plus a combining accent). Under ordinal comparison those sequences are **not** equal. Under [Invariant Culture][] / [Current Culture][] they often **are** equal. If matching must be reliable across differently encoded inputs, normalize text before comparing, or use a comparison policy that matches your data's encoding conventions. + +### Expressions versus text blocks + +In the [Expression Editor][], `"hello" == "hello"` uses [String][] value equality and does not expose a [StringComparison][] argument. Overloads such as `string.Equals(a, b, StringComparison.OrdinalIgnoreCase)` (and text-block **Comparison Type** properties) make the rules explicit—prefer those when case or culture matters. + +Object identity and collection-block matching for non-string types are covered under [object equality][Object Equality]. + +### Defaults in text blocks + +Many text blocks that accept **Comparison Type** default to `Ordinal`. Always check the block's property remarks when behaviour must differ. An invalid [StringComparison][] value (for example `(StringComparison)10`) typically causes [ArgumentException][]. + ### Known Limitations -TODO +* [Current Culture][] and [Current Culture Ignore Case][] results can differ between servers when operating system culture or regional settings differ, and between nodes in a cluster if those settings are not aligned. +* [Current Culture][] reflects the **execution server**, not the Gateway user's browser locale. +* With **Search Options** set to Regex or Pattern Matching and **Comparison Type** set to `CurrentCulture`, some character equivalences (for example `æ` and `ae`) may not evaluate as equal—see each text block's remarks (for example [Contains Text][]). ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings, immutability, and overview of text concepts +* [Casing][] — changing case versus comparing text +* [Object Equality][] — value vs reference equality for objects +* [What is a Culture?][] — culture types overview +* [Invariant Culture][Invariant Culture concept] +* [Current Culture][Current Culture concept] ### Related Data Types -TODO +* [String][] +* [StringComparison][] +* [StringComparer][] +* [CultureInfo][] ### Related Blocks -TODO +* [Contains Text][] +* [Contains Any Text][] +* [Contains All Text][] +* [Find Text][] +* [Find All Text][] +* [Find And Replace Text][] +* [Find And Remove Text][] +* [Get Index Of Text][] +* [Handle Block Exception Matching Message][] +* [Handle Block Exception Matching Messages][] +* [Handle Block Exception Matching Type Name][] +* [Handle Block Exception Matching Type Names][] ### External Documentation -TODO +* [Best practices for comparing strings in .NET][] +* [Compare strings of mixed case][] +* [Performing culture-insensitive string operations][] +* [System.StringComparison][MS StringComparison] +* [System.Globalization.CultureInfo][MS CultureInfo] -[Current Culture]: {{< ref "#current-culture" >}} -[Current Culture Ignore Case]: {{< ref "#current-culture-ignore-case" >}} -[Invariant Culture]: {{< ref "#invariant-culture" >}} -[Invariant Culture Ignore Case]: {{< ref "#invariant-culture-ignore-case" >}} [Ordinal]: {{< ref "#ordinal" >}} [Ordinal Ignore Case]: {{< ref "#ordinal-ignore-case" >}} - -[StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} +[Invariant Culture]: {{< ref "#invariant-culture" >}} +[Invariant Culture Ignore Case]: {{< ref "#invariant-culture-ignore-case" >}} +[Current Culture]: {{< ref "#current-culture" >}} +[Current Culture Ignore Case]: {{< ref "#current-culture-ignore-case" >}} +[Unicode representations]: {{< ref "#unicode-representations" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Casing]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Casing.MainDoc" >}} +[Object Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Objects.ObjectEquality.MainDoc" >}} +[What is a Culture?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.WhatIsACulture.MainDoc" >}} +[Invariant Culture concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture.MainDoc" >}} +[Current Culture concept]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.CurrentCulture.MainDoc" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} +[StringComparer]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparer.MainDoc" >}} +[CultureInfo]: {{< url path="Cortex.Reference.DataTypes.Text.CultureInfo.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} + +[Contains Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsText.MainDoc" >}} +[Contains Any Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAnyText.MainDoc" >}} +[Contains All Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAllText.MainDoc" >}} +[Find Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindText.MainDoc" >}} +[Find All Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindAllText.MainDoc" >}} +[Find And Replace Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndReplaceText.FindAndReplaceText.MainDoc" >}} +[Find And Remove Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndRemoveText.FindAndRemoveText.MainDoc" >}} +[Get Index Of Text]: {{< url path="Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText.MainDoc" >}} +[Handle Block Exception Matching Message]: {{< url path="Cortex.Reference.Blocks.Exceptions.HandleBlock.HandleBlockExceptionMatchingMessage.MainDoc" >}} +[Handle Block Exception Matching Messages]: {{< url path="Cortex.Reference.Blocks.Exceptions.HandleBlock.HandleBlockExceptionMatchingMessages.MainDoc" >}} +[Handle Block Exception Matching Type Name]: {{< url path="Cortex.Reference.Blocks.Exceptions.HandleBlock.HandleBlockExceptionMatchingTypeName.MainDoc" >}} +[Handle Block Exception Matching Type Names]: {{< url path="Cortex.Reference.Blocks.Exceptions.HandleBlock.HandleBlockExceptionMatchingTypeNames.MainDoc" >}} + +[Best practices for comparing strings in .NET]: {{< url path="MSDocs.DotNet.BaseTypes.BestPracticesStrings" >}} +[Compare strings of mixed case]: {{< url path="MSDocs.DotNet.BaseTypes.ChangingCase.CompareStringsOfMixedCase" >}} +[Performing culture-insensitive string operations]: {{< url path="MSDocs.DotNet.Core.Extensions.CultureInsensitiveStringOperations" >}} +[MS StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} +[MS CultureInfo]: {{< url path="MSDocs.DotNet.Api.System.Globalization.CultureInfo" >}} +[ArgumentException]: {{< url path="MSDocs.DotNet.Api.System.ArgumentException" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/formatting.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/formatting.md index 958199470..2e05d34d8 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/formatting.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/formatting.md @@ -1,113 +1,282 @@ --- title: "Formatting" linkTitle: "Formatting" -description: "Information regarding text formatting." +description: "How format providers, format templates, and format items control composite text formatting in CORTEX flows." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +**Formatting** builds a [String][] by combining fixed text with values — for example a greeting, a log line, or a currency amount. In .NET and {{% ctx %}}, that composition is controlled by: + +* A **format provider** — supplies culture-specific rules for numbers, dates, and related types (via [CultureInfo][] and [IFormatProvider][]) +* A **format template** — the composite format string that contains fixed text and placeholders (for example `"Hello {0}"` or `"Total: {0:C2}"`) +* **Format items** — each `{index[,alignment][:formatString]}` placeholder inside the template; the optional `formatString` uses the patterns documented in [Number Formatting][] and [Date and Time Formatting][] + +Use blocks such as [Format Text With Value][], [Format Text With Values][], and [Convert Object To Text][], or expressions such as [String.Format][] and [string interpolation][String interpolation]. Domain-specific patterns for numeric and date/time values are covered on [Number Formatting][] and [Date and Time Formatting][]. -- What formatting is -- How to format text - - Blocks - - Expression -- How does text formatting use DateTime Formatting and Number Formatting? - - Link to Working With -> Date and Time -> Date and Time Formatting - - Link to Working With -> Number -> Number Formatting +| Goal | Prefer | +| --- | --- | +| One value into `{0}` placeholders | [Format Text With Value][], or `String.Format` / `$"…"` | +| Several values into `{0}`, `{1}`, … | [Format Text With Values][], or `String.Format` / `$"…"` | +| Message from an object's named properties | [Convert Object To Text][] with `{Property}` tokens — see [Converting Objects To Text][] | +| Inline expression with format and alignment | [String interpolation][] in the [Expression Editor][] | +| Cross-server consistency | Explicit [Invariant Culture][] as the format provider | +| Fixed regional presentation | [Specific Culture][] (for example `new CultureInfo("en-GB")`) | ## Using Blocks -TODO: +| Block | Placeholder style | Role | +| --- | --- | --- | +| [Format Text With Value][] | `{0}` only | Replaces every `{0}` (optionally with alignment and format string) with a single **Value** | +| [Format Text With Values][] | `{0}`, `{1}`, … | Replaces each indexed format item with the corresponding item from **Values** | +| [Convert Object To Text][] | `{PropertyName}` | Replaces tokens with matching object property values (not composite indexes) | + +All three expose an optional **Format Provider**. When that property is omitted or `null`, they use [Invariant Culture][] (`CultureInfo.InvariantCulture`). Non-text values are converted to their text representation when substituted — see [Converting Objects To Text][]. + +Worked examples live on each block page. For culture-aware date/time display without a composite template, use [Convert Date Time To Text][] — see [Date and Time Formatting][]. -- Explain that blocks can be used - maybe with examples or link to examples within the block documentation - - Convert Object to Text - - Format Text with Value - - Format Text with Values +### Format Text With Value and Format Text With Values + +[Format Text With Value][] and [Format Text With Values][] apply [.NET composite formatting][Composite formatting]: fixed text plus indexed format items. Specifiers after a colon (for example `{0:C2}`, `{1:P0}`) follow the rules on [Number Formatting][] and [Date and Time Formatting][]. + +[Format Text With Value][] accepts only index `0`. A template such as `"Hello {1}"` throws [FormatException][]. [Format Text With Values][] requires that every index in the template is within the bounds of **Values**. + +### Convert Object To Text + +[Convert Object To Text][] is different from composite index formatting: placeholders name **properties** (for example `{Customer.Name}`, `{Amount:C2}`), not list indexes. It does not evaluate arbitrary expressions inside braces and does not support indexing into collection or dictionary properties (for example `{Items[0]}`). For full expression power, prefer [string interpolation][] or [String.Format][] — see [Converting Objects To Text][] and [Convert Object To Text versus string interpolation][]. ## Using Expressions -TODO: How to format using an expression +In the [Expression Editor][], format text with composite formatting APIs and interpolated strings. + +### String.Format + +[`String.Format`][String.Format] takes a format template and one or more arguments: + +```csharp +String.Format("Hello {0}", ($)Name) +String.Format(CultureInfo.InvariantCulture, "Total: {0:C2}", ($)Amount) +String.Format("{0,-12} {1,8:N2}", ($)Label, ($)Value) +``` + +Overloads that take an [IFormatProvider][] apply that provider to culture-sensitive format strings. Prefer an explicit provider when output must not depend on the server's [Current Culture][]. ### String Interpolation -TODO: +An [interpolated string][Interpolated Strings] is declared with `$` and embeds expressions in `{…}`: + +```csharp +$"Hello {($)Name}" +$"Total: {($)Amount:C2}" +$"{($)Label,-12} {($)Value,8:N2}" +``` + +Interpolation uses the same format-item conventions as composite formatting for **alignment** and **format string** clauses. By default, interpolated strings format culture-sensitive values using [Current Culture][]. For invariant or specific-culture results, use `String.Format` with an explicit provider, or an approach documented for culture-specific interpolated strings in [string interpolation in C#][MS Interpolation]. + +For syntax in the Expression Editor (including interpolated verbatim strings with `$` and `@`), see [Interpolated Strings][] and [Verbatim Strings][]. + +#### Convert Object To Text versus string interpolation + +[Convert Object To Text][] is convenient when composing a message from an object's **named properties** and an optional culture-aware **Format Provider**. It is **not** a full substitute for string interpolation: + +* Placeholders are property names, not expressions — you cannot embed calculated expressions inside `{…}` +* Indexing into properties (lists, dictionaries) is not supported +* Nested paths such as `{PaidOff.Total}` are supported when they match properties on the object -- mention that convert object to text is close to behaviour of string interpolation, but not everything is covered - e.g. no expression support or indexing - recommendation is to use string.interpolation inline as more powerful and saves block licensing +When you need expressions, indexing, or richer inline logic, use `$"…"` (or `String.Format`) in an expression. That approach is typically more flexible than adding a Convert Object To Text or Format Text block solely to assemble the string. ## Format Providers -TODO: +A **format provider** implements [IFormatProvider][] and supplies the cultural rules used when format items format numbers, dates, and related types. In practice the provider is almost always a [CultureInfo][] instance. -- what are they -- ways of creating - - CultureInfo.InvariantCulture - - CultureInfo.CurrentCulture - - new CultureInfo("") - - new CultureInfo("en-GB") +Pass a format provider to: + +* Block properties named **Format Provider** on [Format Text With Value][], [Format Text With Values][], and [Convert Object To Text][] +* Expression APIs such as `String.Format(IFormatProvider, string, object[])` and `ToString(string, IFormatProvider)` + +### Obtaining a format provider + +| Approach | Expression | Notes | +| --- | --- | --- | +| Invariant culture | `CultureInfo.InvariantCulture` | Culture-insensitive; fixed patterns. See [Invariant Culture][] | +| Empty culture name | `new CultureInfo("")` | Equivalent to the invariant culture | +| Current culture | `CultureInfo.CurrentCulture` | Reflects the server's regional settings. See [Current Culture][] | +| Specific culture | `new CultureInfo("en-GB")` | Fixed locale regardless of server settings. See [Specific Cultures][] | + +{{% ctx %}} formatting blocks do **not** use [Current Culture][] when **Format Provider** is omitted; they default to [Invariant Culture][]. Expression `String.Format` without a provider, and default interpolated-string formatting, use [Current Culture][]. Align block and expression providers deliberately when both appear in the same flow. + +For culture types in general, see [What is a Culture?][]. ## Format Templates -TODO: +A **format template** (composite format string) is fixed text mixed with one or more [format items][format specifiers]. When formatting runs, each format item is replaced with the string representation of the corresponding argument, optionally shaped by alignment and a type-specific format string. -- Find C# link -- Talk about what a template is and how format parameters are used (e.g. `"{0}"`) -- curly brackets need to be escaped when using the $ and $@ prefixes +Examples: + +| Template | Arguments | Typical result (invariant or as noted) | +| --- | --- | --- | +| `"Hello {0}"` | `"world"` | `Hello world` | +| `"Payment {0:C2}"` | `99.99`, provider `en-US` | `Payment $99.99` | +| `"{0} owed; {1:P0} paid"` | `40`, `0.8`, provider `en-US` | `40 owed; 80 % paid` | + +The same template style is used by [Format Text With Value][], [Format Text With Values][], and `String.Format`. [Convert Object To Text][] uses property-name tokens instead of indexes — see [Converting Objects To Text][]. + +Literal `{` and `}` characters in fixed text must be escaped — see [Escaping braces][]. ## Format Specifiers -TODO: +Each **format item** in a format template has this form: + +```text +{index[,alignment][:formatString]} +``` -- Add table of format specifiers +Matching braces are required. Components in square brackets are optional. -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#Starting +| Component | Required | Meaning | +| --- | --- | --- | +| `index` | Yes | Zero-based argument index (`{0}` is the first value) | +| `alignment` | No | Signed field width; positive = right-align, negative = left-align; padded with spaces | +| `formatString` | No | Type-specific format string (numeric, date/time, enumeration, and so on) | -TODO: Control formatting +Multiple format items can reuse the same index (for example `"{0:X} {0:N}"`). An index outside the argument list throws [FormatException][] at runtime. -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#control-formatting +### Controlling formatting -TODO: Control spacing +The optional `formatString` after the colon controls how the value is converted to text: -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#control-spacing +| Value type | Where to look | Example format item | +| --- | --- | --- | +| Numbers | [Number Formatting][] | `{0:N2}`, `{0:C}`, `{0:0000}` | +| Date and time | [Date and Time Formatting][] | `{0:d}`, `{0:O}`, `{0:dd/MM/yyyy}` | +| Enumerations | [Formatting enumeration values][] | `{0:G}`, `{0:D}` | -TODO: Control alignment +If `formatString` is omitted, the type's general (`G`) formatting applies. Unknown or invalid format strings for the value type throw [FormatException][]. -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#control-alignment +### Controlling spacing and alignment -TODO: Composite formatting +The optional `alignment` component is a signed integer after a comma. If the formatted text is shorter than `|alignment|`, it is padded with spaces to that width. Positive widths right-align; negative widths left-align. If the text is already longer than the width, the width is ignored. -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#the-format-method-in-brief +| Format item | Effect | +| --- | --- | +| `{0,10}` | Right-align in a field of width 10 | +| `{0,-10}` | Left-align in a field of width 10 | +| `{0,10:N1}` | Right-align, then apply numeric format `N1` | -TODO: Format Item +Alignment is applied **after** the value has been converted to text. -- https://learn.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0#the-format-item +### Escaping braces -TODO: Anything else relevant on formatting links above +Opening and closing braces introduce format items. To include a literal brace in the result, double it: `{{` for `{`, and `}}` for `}`. + +For example, `String.Format("{{{0}}}", 42)` yields `{42}` (an opening brace, the formatted value, and a closing brace). + +The same doubling applies in interpolated strings (`$"{{ {($)Value} }}"`) and in interpolated verbatim strings (`$@"…"`). See [Composite formatting][] ([escaping braces][Escaping braces MS]) and [Interpolated Strings][]. ## Remarks +### Culture and format provider defaults + +| Context | Typical default when provider is omitted | +| --- | --- | +| [Format Text With Value][], [Format Text With Values][], [Convert Object To Text][] | [Invariant Culture][] | +| `String.Format` without an [IFormatProvider][] argument | [Current Culture][] | +| Interpolated strings (`$"…"`) | [Current Culture][] for culture-sensitive formatting | + +Mixing block defaults and expression defaults for the same amount or date can produce different separators, currency symbols, or date orders. Set the provider explicitly when results must be stable across servers — see [Invariant Culture][], [Current Culture][], and [Specific Cultures][]. + +### Null and empty values + +In composite formatting, a `null` argument is replaced with an empty string. [Format Text With Values][] documents the same behaviour for null or empty items in **Values**. If a format template is null, empty, or contains no format items, the Format Text blocks set **Text** to the template value unchanged (including `null`). + +### Operating system and culture effects + +Standard numeric and date/time format strings resolve against the format provider. Changing the server's region or Control Panel settings can change results when [Current Culture][] is used. Cross-server flows should prefer [Invariant Culture][] or an explicit [Specific Culture][]. See [Current Culture][] and the operating-system notes on [Date and Time Formatting][]. + ### Known Limitations -TODO +* [Convert Object To Text][] does not support embedding arbitrary expressions or indexing into properties inside format tokens. Use [string interpolation][] or [String.Format][] when those capabilities are required — see [Convert Object To Text versus string interpolation][] and the block's [known limitations][Convert Object To Text Known Limitations]. +* [Format Text With Value][] supports only format index `{0}`. Templates that reference any other index throw [FormatException][]. +* An invalid culture name passed to `new CultureInfo(…)` throws [CultureInfoNotFoundException][]. Formatting and parsing depend on cultures installed on the execution server. ## See Also ### Related Concepts -TODO +* [What is Text?][] — strings and text overview +* [Converting Objects To Text][] — `ToString`, Convert Object To Text, and expression alternatives +* [Number Formatting][] — numeric format providers, templates, and specifiers +* [Date and Time Formatting][] — date and time format patterns +* [What is a Culture?][] — invariant, current, and specific cultures +* [Invariant Culture][] — culture-insensitive formatting defaults +* [Current Culture][] — server regional settings +* [Specific Cultures][] — fixed locale formatting +* [Interpolated Strings][] — interpolation in the Expression Editor ### Related Data Types -TODO +* [String][] +* [CultureInfo][] +* [IFormatProvider][] ### Related Blocks -TODO +* [Format Text With Value][] +* [Format Text With Values][] +* [Convert Object To Text][] +* [Convert Date Time To Text][] +* [Join Text][] ### External Documentation -TODO +* [String.Format][] +* [Composite formatting][] +* [String interpolation in C#][MS Interpolation] +* [Standard numeric format strings][] +* [Custom numeric format strings][] +* [Standard date and time format strings][] +* [IFormatProvider][MS IFormatProvider] + +[format specifiers]: {{< ref "#format-specifiers" >}} +[String interpolation]: {{< ref "#string-interpolation" >}} +[Convert Object To Text versus string interpolation]: {{< ref "#convert-object-to-text-versus-string-interpolation" >}} +[Escaping braces]: {{< ref "#escaping-braces" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Converting Objects To Text]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.ConvertingObjectsToText.MainDoc" >}} +[Number Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Numbers.NumberFormatting.MainDoc" >}} +[Date and Time Formatting]: {{< url path="Cortex.Reference.Concepts.WorkingWith.DateAndTime.DateAndTimeFormatting.MainDoc" >}} +[What is a Culture?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.WhatIsACulture.MainDoc" >}} +[Invariant Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.InvariantCulture.MainDoc" >}} +[Current Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.CurrentCulture.MainDoc" >}} +[Specific Cultures]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.SpecificCultures.MainDoc" >}} +[Specific Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Culture.SpecificCultures.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[Interpolated Strings]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.InterpolatedStrings" >}} +[Verbatim Strings]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.VerbatimStrings" >}} + +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[CultureInfo]: {{< url path="Cortex.Reference.DataTypes.Text.CultureInfo.MainDoc" >}} +[IFormatProvider]: {{< url path="Cortex.Reference.DataTypes.Text.IFormatProvider.MainDoc" >}} + +[Format Text With Value]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValue.MainDoc" >}} +[Format Text With Values]: {{< url path="Cortex.Reference.Blocks.Text.FormatText.FormatTextWithValues.MainDoc" >}} +[Convert Object To Text]: {{< url path="Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText.MainDoc" >}} +[Convert Object To Text Known Limitations]: {{< url path="Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText.KnownLimitations" >}} +[Convert Date Time To Text]: {{< url path="Cortex.Reference.Blocks.DateAndTime.ConvertDateTime.ConvertDateTimeToText.MainDoc" >}} +[Join Text]: {{< url path="Cortex.Reference.Blocks.Text.JoinText.JoinText.MainDoc" >}} + +[String.Format]: {{< url path="MSDocs.DotNet.Api.System.String.Format" >}} +[Composite formatting]: {{< url path="MSDocs.DotNet.BaseTypes.CompositeFormatting.MainDoc" >}} +[Escaping braces MS]: {{< url path="MSDocs.DotNet.BaseTypes.CompositeFormatting.EscapingBraces" >}} +[MS Interpolation]: {{< url path="MSDocs.CSharp.Interpolation" >}} +[MS IFormatProvider]: {{< url path="MSDocs.DotNet.Api.System.IFormatProvider" >}} +[FormatException]: {{< url path="MSDocs.DotNet.Api.System.FormatException" >}} +[CultureInfoNotFoundException]: {{< url path="MSDocs.DotNet.Api.System.Globalization.CultureInfoNotFoundException" >}} +[Formatting enumeration values]: {{< url path="MSDocs.DotNet.Api.System.Enum.FormattingEnumerationValues" >}} +[Standard numeric format strings]: {{< url path="MSDocs.DotNet.BaseTypes.StandardNumericFormatStrings" >}} +[Custom numeric format strings]: {{< url path="MSDocs.DotNet.BaseTypes.CustomNumericFormatStrings" >}} +[Standard date and time format strings]: {{< url path="MSDocs.DotNet.Api.System.DateTimeOffset.FormatStrings" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md index 4902f3664..588468cb7 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md @@ -1,48 +1,168 @@ --- title: "Pattern Matching Syntax" linkTitle: "Pattern Matching Syntax" -description: "Information regarding pattern matching syntax." +description: "How wildcard pattern matching works in CORTEX: * and ? wildcards, SearchOptions.PatternMatching, and how it differs from LiteralText and Regex." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO +In {{% ctx %}}, **pattern matching** is a simple wildcard language for matching text. You enable it by setting [Search Options][SearchOptions] to [PatternMatching][] on blocks that support search. It is lighter than [.NET regular expressions][Regex Syntax] and more flexible than an exact [LiteralText][] match. + +| Approach | When to use | Wildcards | +| --- | --- | --- | +| [LiteralText][] | Exact character match of the search text (substring "contains" behaviour on many blocks) | None — every character is literal | +| [PatternMatching][] | Simple wildcards for text or file/folder **names** (for example `*.txt`, `Uptime is * hours.`) | `*` and `?` only | +| [Regex][SearchOptions Regex] | Full pattern language (quantifiers, groups, character classes, and so on) | Full [.NET Regex Syntax][Regex Syntax] | + +Pattern matching is available only through block [Search Options][SearchOptions] — there is no separate pattern-matching expression syntax. Pair it with a [Comparison Type][Equality] when the block exposes one. For an overview of the three modes, see [What is Text?][]. + +## Wildcard characters + +When [Search Options][SearchOptions] is [PatternMatching][], the search text is interpreted as follows: + +| Character | Matches | Examples | +| --- | --- | --- | +| `*` | Zero or more characters | `"B*wn"` matches `"Brown"`; `"*"` matches any text of any length (including empty) | +| `?` | Zero or one character | `"?he"` matches `"The"` and `"the"`; `"hour?."` matches `"hours."` or `"hour."` | +| Any other character | That exact character (literal) | `"."` matches a period; `"Fox"` matches only those three letters in that order | + +These rules match the wildcard model used by .NET file search APIs such as [DirectoryInfo.GetFiles][] (`*` = zero or more characters; `?` = zero or one character). Pattern matching is **not** a regular expression: metacharacters such as `.`, `+`, `|`, and `()` have no special meaning unless you switch to [Regex][SearchOptions Regex]. + +### Examples in text + +Against the text `"The quick brown fox jumps over the lazy dog"` with [PatternMatching][] (as on [Contains Any Text][]): + +| Pattern | Result | Notes | +| --- | --- | --- | +| `"?he"` | Match | `?` covers `T` / `t`; remainder is `he` | +| `"Q?ick"` | Match | Optional single character between `Q` and `ick` | +| `"B*?wn"` | Match | `*` covers `ro`; `?` can cover zero characters | +| `"Fox"` | No match under ordinal casing | Literal letters; `"fox"` differs in case unless ignore-case comparison is used | + +Further worked examples for starts-with / contains / ends-with patterns appear on [Find Text][]. + +### Examples for files and folders + +Use pattern matching with [Get Folder Content][] to filter by **file or folder name**, and with [Search File][] / [Search Files][] to find lines of text inside files. Folder listing matches **names only** — see [Matching scope for folder content][]. -- What is pattern matching? - - how is it different to contains or regex -- Pattern matching can only be used in blocks -- `*` is 0 or more -- `?` is 0 or 1 -- to use these as literals use `\\*` `\\?` -- Common pattern matching - - `**/*.ext` (Check that this works) - - Common file extensions to search for, how to search for them `*.txt`, `*.pdf` - - Link to Files and Folders for list of common extensions +| Goal | Pattern | Block notes | +| --- | --- | --- | +| Any name | `"*"` | [Get Folder Content][] example lists all folders whose names match `"*"` | +| Text files by extension | `"*.txt"` | Matches names ending with `.txt` (name only — not a path glob) | +| PDF files | `"*.pdf"` | Same idea for other extensions | +| Log-style phrase in file content | `"Uptime is * hours."` | [Search File][] example | +| Optional trailing `s` | `"Uptime is * hour?."` | [Search Files][] example | + +Common extensions (`.txt`, `.log`, `.csv`, `.json`, `.xml`, `.pdf`, `.docx`, `.xlsx`, `.zip`, and others) are listed under [What are Files and Folders?][]. Do **not** put `*` or `?` in path properties — wildcards belong in the search pattern; see [Paths][]. + +## Pattern matching versus other search modes + +| Topic | LiteralText | PatternMatching | Regex | +| --- | --- | --- | --- | +| Language | Exact characters | `*` and `?` only | Full .NET regex | +| Typical use | Fixed phrases | File-style or simple fuzzy text patterns | Complex structure, groups, alternation | +| Invalid pattern errors | N/A | N/A (wildcards only) | Invalid patterns can throw [RegexParsingFailedException][] | +| Search timeout | Not applied as for PatternMatching/Regex on many Contains blocks | Can throw [RegexMatchTimeoutException][] if the search exceeds the block's documented limit (often `30` seconds) | Same timeout behaviour on those blocks | + +"Contains" is a **block operation** (does this text contain a match?). [LiteralText][], [PatternMatching][], and [Regex][SearchOptions Regex] are **how** the search text is interpreted via [SearchOptions][]. ## Remarks +### Available only on blocks + +Set [SearchOptions][] to [PatternMatching][] on supporting Text and Files & Folders blocks. Pattern matching is not a standalone expression language in the [Expression Editor][]. + +### Matching scope for folder content + +[Get Folder Content][] matches **file or folder names only**, not the full path. To include subfolders, set the block's **Recursive** property to `true`. A pattern such as `"**/*.txt"` is **not** a recursive path glob here: `*` and `?` apply to the name string, and path separators in the pattern do not walk directories. Prefer `"*.txt"` with **Recursive** when you need matching names under a folder tree. + +### Comparison Type + +When a block exposes [Comparison Type][Equality], culture and casing rules still apply. Prefer [Ordinal][] or [Ordinal Ignore Case][] for machine-oriented names and patterns unless you intentionally need linguistic rules. See [Equality][]. + +### Search timeout + +On many Text and Files & Folders blocks, a [PatternMatching][] (or [Regex][SearchOptions Regex]) search that runs longer than the documented limit (commonly `30` seconds) throws [RegexMatchTimeoutException][]. Check each block's Exceptions table for the exact timeout. + ### Known Limitations -TODO +* If [Search Options][SearchOptions] is [PatternMatching][] (or [Regex][SearchOptions Regex]) and [Comparison Type][Equality] is [Current Culture][], some character equivalences (for example `æ` and `ae`) may not evaluate as equal — see each text or folder block's remarks (for example [Contains Text][]). ## See Also ### Related Concepts -TODO +* [What is Text?][] — LiteralText, Regex, and PatternMatching overview +* [Equality][] — Comparison Type and culture/casing rules +* [Regex Syntax][] — full regular expression language +* [What are Files and Folders?][] — common extensions and Files & Folders overview +* [Paths][] — path rules; wildcards must not appear in path properties ### Related Data Types -TODO +* [SearchOptions][] +* [String][] +* [StringComparison][] +* [TextToFind][] +* [FileMatch][] ### Related Blocks -TODO +* [Contains Text][] +* [Contains Any Text][] +* [Contains All Text][] +* [Find Text][] +* [Find All Text][] +* [Find And Replace Text][] +* [Find And Remove Text][] +* [Get Index Of Text][] +* [Get Folder Content][] +* [Search File][] +* [Search Files][] ### External Documentation -TODO +* [DirectoryInfo.GetFiles][] — .NET wildcard (`*` / `?`) search patterns for file names +* [RegexMatchTimeoutException][] +* [Naming files, paths, and namespaces][] + +[Matching scope for folder content]: {{< ref "#matching-scope-for-folder-content" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} +[Regex Syntax]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.RegexSyntax.MainDoc" >}} +[What are Files and Folders?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.FilesAndFolders.WhatAreFilesAndFolders.MainDoc" >}} +[Paths]: {{< url path="Cortex.Reference.Concepts.WorkingWith.FilesAndFolders.Paths.MainDoc" >}} +[Ordinal]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.Ordinal" >}} +[Ordinal Ignore Case]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.OrdinalIgnoreCase" >}} +[Current Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.CurrentCulture" >}} + +[SearchOptions]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.MainDoc" >}} +[LiteralText]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.LiteralText" >}} +[PatternMatching]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.PatternMatching" >}} +[SearchOptions Regex]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.Regex" >}} +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} +[TextToFind]: {{< url path="Cortex.Reference.DataTypes.Text.TextToFind.MainDoc" >}} +[FileMatch]: {{< url path="Cortex.Reference.DataTypes.FilesAndFolders.FileMatch.MainDoc" >}} + +[Contains Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsText.MainDoc" >}} +[Contains Any Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAnyText.MainDoc" >}} +[Contains All Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAllText.MainDoc" >}} +[Find Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindText.MainDoc" >}} +[Find All Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindAllText.MainDoc" >}} +[Find And Replace Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndReplaceText.FindAndReplaceText.MainDoc" >}} +[Find And Remove Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndRemoveText.FindAndRemoveText.MainDoc" >}} +[Get Index Of Text]: {{< url path="Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText.MainDoc" >}} +[Get Folder Content]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.GetFolderContent.GetFolderContent.MainDoc" >}} +[Search File]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFile.MainDoc" >}} +[Search Files]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFiles.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[RegexParsingFailedException]: {{< url path="Cortex.Reference.Exceptions.Text.Regex.RegexParsingFailedException.MainDoc" >}} + +[DirectoryInfo.GetFiles]: {{< url path="MSDocs.DotNet.Api.System.IO.DirectoryInfo.GetFiles" >}} +[RegexMatchTimeoutException]: {{< url path="MSDocs.DotNet.Api.System.Text.RegularExpressions.RegexMatchTimeoutException" >}} +[Naming files, paths, and namespaces]: {{< url path="MSDocs.Windows.Apps.Win32.DesktopTechnologies.DataAccessAndStorage.LocalFileSystems.NamingFilesPathsAndNamespaces.MainDoc" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md index 8e8caee71..87fe0abd3 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md @@ -1,216 +1,324 @@ --- title: "Regex Syntax" linkTitle: "Regex Syntax" -description: "Information regarding .Net regex syntax." +description: "How .NET regular expression syntax works in CORTEX: character classes, quantifiers, groups, options, timeouts, and how Regex differs from LiteralText and PatternMatching." --- # {{% param title %}} -{{< workinprogress >}} - ## Summary -TODO: +In {{% ctx %}}, **regular expressions (regex)** are patterns used to match, find, or replace text. You enable them by setting [Search Options][SearchOptions] to [Regex][SearchOptions Regex] on blocks that support search. The language is [.NET regular expressions][NET Regular Expressions] (`System.Text.RegularExpressions`). + +| Approach | When to use | Wildcards / pattern language | +| --- | --- | --- | +| [LiteralText][] | Exact character match of the search text (substring "contains" behaviour on many blocks) | None — every character is literal | +| [PatternMatching][] | Simple wildcards for text or file/folder **names** (for example `*.txt`) | `*` and `?` only — see [Pattern Matching Syntax][] | +| [Regex][SearchOptions Regex] | Complex structure, groups, character classes, alternation, replacements | Full [.NET regex language][NET Regex Quick Reference] (this page) | -- What is regex matching? - - how is it different to contains or pattern matching -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expressions -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference -- Common regex expressions - - urls, emails, etc - - https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-example-scanning-for-hrefs - - and subsequent links -- State that we specify a timeout to prevent dos attacks -- Talk about no current support for old g2 syntax - may add in future +"Contains" is a **block operation** (does this text contain a match?). [LiteralText][], [PatternMatching][], and [Regex][SearchOptions Regex] are **how** the search text is interpreted via [SearchOptions][]. For an overview of the three modes, see [What is Text?][]. -TODO: Options +Regex is available through block [Search Options][SearchOptions] and through regex properties such as the SSH [TerminalPrompt][SSH TerminalPrompt] and Telnet [TerminalPrompt][Telnet TerminalPrompt] — it is not a separate expression language. Pair it with a [Comparison Type][Equality] when the block exposes one. Prefer a [verbatim string][Verbatim] (`@"…"`) in the [Expression Editor][] when a pattern contains many backslashes. -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options -- https://learn.microsoft.com/en-us/dotnet/standard/base-types/miscellaneous-constructs-in-regular-expressions +Invalid patterns throw [RegexParsingFailedException][]. On supporting blocks, searches that run too long throw [RegexMatchTimeoutException][] — timeouts limit unbounded matching that can be used as a denial-of-service vector. See [Search timeout][]. -Links: +The tables below summarise the main .NET constructs. For the full language reference, options, and best practices, see [External Documentation][]. -- Regex Best Practices - https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices +## Common patterns -### Character Classes +These patterns are **illustrative** starting points. Production validation (especially for email addresses and URLs) is more complex than a short expression can cover; prefer well-reviewed patterns and tests for your data. -TODO: +| Goal | Example pattern | Notes | +| --- | --- | --- | +| One or more digits | `\d+` | Matches `"7"` in `"Cortex 7"` | +| Word characters | `\w+` | Letters, digits, and connectors such as underscore | +| Simple email-shaped text | `[\w.-]+@[\w.-]+\.\w+` | Not a full RFC validator; enough for many coarse filters | +| `http` / `https` URL-shaped text | `https?://[^\s]+` | Stops at whitespace; does not validate hosts or schemes fully | +| Quoted `href` value | `href\s*=\s*(?:["'](?[^"']*)["']\|(?\S+))` | See the .NET [scanning for HREFs][Scanning for HREFs] example for a worked walk-through | -- Include information about any builtin snippets for each regex +## Character Classes -A character class matches any of a set of characters. Character classes include the language elements listed in the following table. +A character class matches any one character from a set. Character classes include the language elements listed in the following table. For Unicode categories and named blocks used with `\p{…}` / `\P{…}`, see [Supported Unicode general categories][] and [Supported named blocks][]. | Syntax | Description | Pattern | Matches | |---|---|---|---| -| `[characters]` | Matches any character found in characters. | `[oz]` | o in Cortex | -| `[^characters]` | Matches any character not found in characters. | `[^oz]` | C, r, t, e, x in Cortex | -| `[first-last]` | Matches any character in the range of characters from first to last. | `[A-C]` | C in Cortex | -| `.` | Wildcard. Matches any character except \n. | `C.r` | Cor in Cortex | -| `\p{category}` | Matches any character in a category of Unicode characters, specified by category. To see what you can use for category, please check the supported Unicode general categories and the supported named blocks. | `\p{Lu}` | C in Cortex | -| `\P{category}` | Matches any character not in a category of Unicode characters, specified by category. To see what you can use for category, please check the supported Unicode general categories, and the supported named blocks. | `\P{Lu}` | o, r, t, e, x in Cortex | -| `\w` | Matches any letter, decimal digit, or an underscore. | `\w` | C, o, r, t, e, x in Cortex ! | -| `\W` | Matches any character except a letter, decimal digit, or an underscore. | `\W` | ! in Cortex ! | -| `\s` | Matches any white-space character. | `\w\s` | x  in Cortex ! | -| `\S` | Matches any character except a white-space character. | `\s\S` |  ! in Cortex ! | -| `\d` | Matches any decimal digit. | `\d` | 7 in Cortex 7! | -| `\D` | Matches any character except a decimal digit. | `\D` | C, o, r, t, e, x,  , ! in Cortex 7! | +| `[characters]` | Matches any character found in *characters*. | `[oz]` | `o` in `Cortex` | +| `[^characters]` | Matches any character not found in *characters*. | `[^oz]` | `C`, `r`, `t`, `e`, `x` in `Cortex` | +| `[first-last]` | Matches any character in the range from *first* to *last*. | `[A-C]` | `C` in `Cortex` | +| `.` | Wildcard. Matches any character except `\n` (unless single-line mode is on). | `C.r` | `Cor` in `Cortex` | +| `\p{category}` | Matches any character in a Unicode general category or named block. | `\p{Lu}` | `C` in `Cortex` | +| `\P{category}` | Matches any character not in that category or named block. | `\P{Lu}` | `o`, `r`, `t`, `e`, `x` in `Cortex` | +| `\w` | Matches any word character. | `\w` | `C`, `o`, `r`, `t`, `e`, `x` in `Cortex !` | +| `\W` | Matches any non-word character. | `\W` | `!` in `Cortex !` | +| `\s` | Matches any white-space character. | `\w\s` | `x ` in `Cortex !` | +| `\S` | Matches any non-white-space character. | `\s\S` | ` !` in `Cortex !` | +| `\d` | Matches any decimal digit. | `\d` | `7` in `Cortex 7!` | +| `\D` | Matches any character except a decimal digit. | `\D` | `C`, `o`, `r`, `t`, `e`, `x`, ` `, `!` in `Cortex 7!` | + +## Character Escapes + +The following table lists common character escapes supported by regular expressions in .NET. A backslash that is not part of a defined escape matches the following character literally (for example `\*` matches `*`). -https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#SupportedUnicodeGeneralCategories -https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#supported-named-blocks +| Syntax | Description | Pattern | Matches | +|---|---|---|---| +| `\r` | Matches a carriage return. | `\r\n(\w+)` | `\r\nCortex` in `\r\nCortex with\na new line` | +| `\n` | Matches a newline. | `\r\n(\w+)` | `\r\nCortex` in `\r\nCortex with\na new line` | +| `\t` | Matches a tab. | `(\w+)\t` | `Cortex1\t`, `Cortex2\t` in `Cortex1\tCortex2\t` | +| `[\b]` | Matches a backspace. Must be enclosed in brackets for this meaning (`\b` alone is a word boundary). | `[\b]{3,}` | `\b\b\b\b` in `\b\b\b\b` | +| `\f` | Matches a form feed. | `[\f]{2,}` | `\f\f\f` in `\f\f\f` | +| `\e` | Matches an escape. | `\e` | `\x001B` in `\x001B` | +| `\v` | Matches a vertical tab. | `[\v]{2,}` | `\v\v\v` in `\v\v\v` | +| `\a` | Matches the bell character. | `\a` | `\u0007` in `Cortex '\u0007'` | +| `\octal` | Matches a character given as an octal code. | `\w\040\w` | `x C` in `Cortex Cortex` | +| `\xhex` | Matches a character given as a two-digit hexadecimal code. | `\w\x20\w` | `x C` in `Cortex Cortex` | +| `\uunicode` | Matches a Unicode character given as a four-digit hexadecimal code. | `\w\u0020\w` | `x C` in `Cortex Cortex` | +| `\ccharacter` | Matches an ASCII control character specified by *character*. | `\cC` | `\x0003` in `\x0003` | + +## Quantifiers + +A quantifier specifies how many instances of the previous element (character, group, or character class) must be present for a match. Quantifiers ending in `?` are **lazy** (match as few times as possible); the others are **greedy**. -### Character Escapes +| Syntax | Description | Pattern | Matches | +|---|---|---|---| +| `*` | Matches the previous element zero or more times. | `co*rtex` | `crtex`, `cortex`, `coortex`, `coooortex` in `crtex cortex coortex coooortex` | +| `+` | Matches the previous element one or more times. | `co+rtex` | `cortex`, `coortex`, `coooortex` in `crtex cortex coortex coooortex` | +| `?` | Matches the previous element zero or one time. | `co?rtex` | `crtex`, `cortex` in `crtex cortex coortex coooortex` | +| `{n}` | Matches the previous element exactly *n* times. | `co{2}rtex` | `coortex` in `crtex cortex coortex coooortex` | +| `{n,}` | Matches the previous element at least *n* times. | `co{2,}rtex` | `coortex`, `coooortex` in `crtex cortex coortex coooortex` | +| `{n,m}` | Matches the previous element at least *n* and at most *m* times. | `co{1,2}rtex` | `cortex`, `coortex` in `crtex cortex coortex coooortex` | +| `*?` | Lazy `*`. | `cort(ex)*?` | `cort` in `cortexexex` | +| `+?` | Lazy `+`. | `cort(ex)+?` | `cortex` in `cortexexex` | +| `??` | Lazy `?`. | `cort(ex)??` | `cort` in `cortexexex` | +| `{n,}?` | Lazy `{n,}`. | `cort(ex){2,}?` | `cortexex` in `cortexexex` | +| `{n,m}?` | Lazy `{n,m}`. | `cort(ex){1,3}?` | `cortex` in `cortexexex` | + +## Anchors + +Anchors cause a match to succeed or fail based on the current position in the string without consuming characters. -TODO: +| Syntax | Description | Pattern | Matches | +|---|---|---|---| +| `^` | Matches the beginning of the input (or of each line in multiline mode). | `^\w{3}` | `Cor` in `Cortex` | +| `$` | Matches the end of the input, or the point before a final `\n` (or end of each line in multiline mode). | `\w{3}$` | `tex` in `Cortex` | +| `\A` | Matches the beginning of the input. Unaffected by multiline mode. | `\A\w{3}` | `Cor` in `Cortex` | +| `\z` | Matches the end of the input only. | `\w{3}\z` | `tex` in `Cortex` | +| `\Z` | Matches the end of the input, or the point before a final `\n`. Unaffected by multiline mode. | `\w{3}\Z` | `tex` in `Cortex` | +| `\G` | Matches where the previous match ended (contiguous matches). | `\G\D*\s` | `Cortex `, `reads ` in `Cortex reads 7 files` | +| `\b` | Matches a word boundary (between a `\w` and a `\W`). | `\b\w+\s\w+\b` | `Cortex reads`, `Cortex writes` in `Cortex reads Cortex writes` | +| `\B` | Matches a non-word-boundary position. | `\Brt\w*\b` | `rtex`, `rtex` in `Cortex reads Cortex writes` | -- Include information about any builtin snippets for each regex +## Grouping Constructs -The following table lists the character escapes supported by regular expressions in .NET. +Grouping constructs delineate subexpressions and typically capture parts of the input. Captured groups are available on outputs such as [Match][] and [Group][] from blocks like [Find All Text][]. | Syntax | Description | Pattern | Matches | |---|---|---|---| -| `\r` | Matches a carriage return. | `\r\n(\w+)` | \r\nCortex in \r\nCortex with\na new line | -| `\n` | Matches a newline. | `\r\n(\w+)` | \r\nCortex in \r\nCortex with\na new line | -| `\t` | Matches a tab. | `(\w+)\t` | Cortex1\t, Cortex2\t in Cortex1\tCortex2\t | -| `[\b]` | Matches a backspace. Note that it must be enclosed in brackets to have this meaning. | `[\b]{3,}` | \b\b\b\b in \b\b\b\b | -| `\f` | Matches a form feed. | `[\f]{2,}` | \f\f\f in \f\f\f | -| `\e` | Matches an escape. | `\e` | \x001B in \x001B | -| `\v` | Matches a vertical tab. | `[\v]{2,}` | \v\v\v in \v\v\v | -| `\a` | Matches the bell character. | `\a` | \u0007 in Cortex '\u0007' | -| `\octal` | Matches a character, where octal is the octal representation of that character. | `\w\040\w` | x C in Cortex Cortex | -| `\xhex` | Matches a character, where hex is the two-digit hexadecimal representation of that character. | `\w\x20\w` | x C in Cortex Cortex | -| `\uunicode` | Matches a Unicode character, where unicode is the four digit hexadecimal representation of that Unicode character. | `\w\u0020\w` | x C in Cortex Cortex | -| `\ccharacter` | Matches an ASCII control character specified by character. | `\cC` | \x0003 in \x0003 | +| `(subpattern)` | Captures *subpattern* as an unnamed group. | `(\w)\1` | `oo` in `Coortex` | +| `(?subpattern)` | Captures *subpattern* as a named group. | `(?\w)\k` | `oo` in `Coortex` | +| `(?subpattern)` | Balancing group definition for nested constructs. *name* can be omitted to capture as an unnamed group. | `(((?)[^<]*)+([^<]*(?))+)+(?(open)(?!))` | `<span>Cortex this is included</span>`, `<span>Cortex this is included too</span>` in `not included <span>Cortex this is included</span> not included either <span>Cortex this is included too</span>` | +| `(?:subpattern)` | Non-capturing group. | `Cortex\s(?:include)?` | `Cortex include`, `Cortex ` in `Cortex include Cortex not include` | +| `(?enabled-disabled:subpattern)` | Matches *subpattern* with different inline options. See [Regular expression options][]. | `(?i:c\|v)(ortex)` | `cortex`, `Cortex`, `Vortex` in `cortex Cortex Vortex CORTEX` | +| `(?=subpattern)` | Zero-width positive look-ahead. | `\w+(?=ex\b)` | `Cort`, `Vort` in `Cortex Vortex Balloon` | +| `(?!subpattern)` | Zero-width negative look-ahead. | `\b\w+\.(?!exe)\w+\b` | `cortex.jpg`, `cortex.html` in `cortex.jpg cortex.html .*.html cortex.exe` | +| `(?<=subpattern)` | Zero-width positive look-behind. | `(?<=\(\$\))\w+` | `variable`, `22` in `($)variable ($)22 ($)--` | +| `(?subpattern)` | Atomic group; prevents backtracking over *subpattern*. | `[cv](?>o+r+)` | `cor`, `coor`, `vor` in `cortex coortex vortex gortex` | + +## Back-reference Constructs -### Quantifiers +A back-reference matches text that a previous capturing group already matched. -TODO: +| Syntax | Description | Pattern | Matches | +|---|---|---|---| +| `\number` | Matches the value of a previously captured group by number. | `\b(\w)\w*\1\b` | `xcortex`, `that` in `Finds all words like xcortex that start and end with the same letter` | +| `\k` | Matches the value of a previously captured named group. | `(?\p{P})\w+\k` | `!cortex!`, `?cortex?` in `!cortex! ?cortex? XcortexX` | -- Include information about any builtin snippets for each regex +## Alternation Constructs -A quantifier specifies how many instances of the previous element (which can be a character, a group, or a character class) must be present in the input string for a match to occur. Quantifiers include the language elements listed in the following table. +Alternation enables either/or matching. | Syntax | Description | Pattern | Matches | |---|---|---|---| -| `*` | Matches previous element zero or more times. | `co*rtex` | crtex, cortex, coortex, coooortex in crtex cortex coortex coooortex | -| `+` | Matches previous element one or more times. | `co+rtex` | cortex, coortex, coooortex in crtex cortex coortex coooortex | -| `?` | Matches previous element zero or one times. | `co?rtex` | crtex, cortex in crtex cortex coortex coooortex | -| `{n}` | Matches previous element exactly n times. | `co{2}rtex` | coortex in crtex cortex coortex coooortex | -| `{n,}` | Matches previous element at least n times. | `co{2,}rtex` | coortex, coooortex in crtex cortex coortex coooortex | -| `{n,m}` | Matches previous element at least n times and at most m times. | `co{1,2}rtex` | cortex, coortex in crtex cortex coortex coooortex | -| `*?` | Matches previous element zero or more times, but as few times as possible. | `cort(ex)*?` | cort in cortexexex | -| `+?` | Matches previous element one or more times, but as few times as possible. | `cort(ex)+?` | cortex in cortexexex | -| `??` | Matches previous element zero or one time, but as few times as possible. | `cort(ex)??` | cort in cortexexex | -| `{n,}?` | Matches previous element at least n times, but as few times as possible. | `cort(ex){2,}?` | cortexex in cortexexex | -| `{n,m}?` | Matches previous element at least n times and at most m times, but as few times as possible. | `cort(ex){1,3}?` | cortex in cortexexex | +| `\|` | Logical or. Matches any of the alternatives it separates. | `(c\|v)ortex` | `cortex`, `vortex` in `cortex vortex xortex` | +| `(?(subpattern)yes\|no)` | If *subpattern* matches as a zero-width assertion, match *yes*; otherwise match *no* (`\|no` is optional). | `\b(?(\w+tez\b)\w{3}\|cortex)` | `cortex`, `cor` in `cortex cortez vortex` | +| `(?(group)yes\|no)` | If a previous group (*number* or *name*) was captured, match *yes*; otherwise match *no*. | `(?(\(\$\)))\w+\|'\w+'` | `cortex`, `'cortex'` in `($)cortex 'cortex'` | -### Anchors +## Substitutions -TODO: +Substitutions are language elements used in **replacement** patterns (for example [Find And Replace Text][]), not in the search pattern itself. -- Include information about any builtin snippets for each regex +| Syntax | Description | Pattern | Replacement | Result | +|---|---|---|---|---| +| `$number` | Substitutes the value of a numbered group. | `\b(\w+)(\s)(\w+)\b` | `$3$2$1` | `Cortex Great` becomes `Great Cortex` | +| `${name}` | Substitutes the value of a named group. | `\b(?\w+)(\s)(?\w+)\b` | `${word2} ${word1}` | `Cortex Great` becomes `Great Cortex` | +| `$$` | Substitutes a literal `$`. | `\b(dollar)` | `$$` | `(dollar)name` becomes `($)name` | +| `$&` | Substitutes the entire match. | `\w+` | `\*\*$&\*\*` | `Cortex` becomes `\*\*Cortex\*\*` | +| ``$` `` | Substitutes all input text before the match. | `#+` | ``$` `` | `Co##rtex` becomes `CoCortex` | +| `$'` | Substitutes all input text after the match. | `#+` | `$'` | `Cort##ex` becomes `Cortexex` | +| `$+` | Substitutes the last group captured. | `Co(r)` | `$+` | `CoCortex` becomes `Cortex` | +| `$_` | Substitutes the entire input. | `\w+?` | `$_` | `Cortex` becomes `Cortex Cortex Cortex Cortex Cortex Cortex` | -Anchors cause a match to succeed or fail depending on the current position in the string. +## Regular expression options -| Syntax | Description | Pattern | Matches | -|---|---|---|---| -| `^` | Matches the beginning of the input. | `^\w{3}` | Cor in Cortex | -| `$` | Matches the end of the input, or the point before a final \n at the end of the input. | `\w{3}$` | tex in Cortex | -| `\A` | Matches the beginning of the input. Identical to ^, except it is unaffected by the multi-line option. | `\A\w{3}` | Cor in Cortex | -| `\z` | Matches the end of the input, without exception. | `\w{3}\z` | tex in Cortex | -| `\Z` | Matches the end of the input, or the point before a final \n at the end of the input. Identical to $, except it is unaffected by the multi-line option. | `\w{3}\Z` | tex in Cortex | -| `\G` | Matches the point that the previous match ended. Used to find contiguous matches. | `\G\D*\s` | Cortex , reads  in Cortex reads 7 files | -| `\b` | Matches any word boundary. Specifically, any point between a \w and a \W. | `\b\w+\s\w+\b` | Cortex reads, Cortex writes in Cortex reads Cortex writes | -| `\B` | Matches any point that is not a word boundary. Specifically, any point not between a \w and a \W. | `\Brt\w*\b` | rtex, rtex in Cortex reads Cortex writes | +.NET supports [regular expression options][NET Regular Expression Options] that change matching behaviour. In {{% ctx %}} you typically control casing and culture with the block's [Comparison Type][Equality]. You can also enable or disable options **inline** in the pattern: -### Grouping Constructs +| Inline | `RegexOptions` | Effect | +| --- | --- | --- | +| `i` | IgnoreCase | Case-insensitive matching | +| `m` | Multiline | `^` and `$` match the start and end of each line | +| `s` | Singleline | `.` matches every character, including `\n` | +| `n` | ExplicitCapture | Only named or explicitly numbered groups capture | +| `x` | IgnorePatternWhitespace | Ignore unescaped white space in the pattern; allow `#` comments | -TODO: +Inline forms: -- Include information about any builtin snippets for each regex +* `(?imnsx)` — enable options for the rest of the pattern (or until another inline option changes them) +* `(?-imnsx)` — disable options +* `(?imnsx-imnsx:subpattern)` — apply options only to *subpattern* -Grouping constructs delineate sub-expressions of a regular expression and typically capture sub-strings of an input string. Grouping constructs include the language elements listed in the following table. +Example: `(?i)cortex` matches `Cortex`, `CORTEX`, and `cortex`. -| Syntax | Description | Pattern | Matches | -|---|---|---|---| -| `(subpattern) `| Captures subpattern as an unnamed group. | `(\w)\1` | oo in Coortex | -| `(?subpattern)` | Captures subpattern as a named group specified by name. | `(?\w)\k` | oo in Coortex | -| `(?subpattern)` | Balancing group definition. This allows nested constructs to be matched, such as parentheses or HTML tags. The previously defined group to balance against is specified by previous. Captures subpattern as a named group specified by name, or name can be omitted to capture as an unnamed group. | `(((?)[^<]*)+([^<]*(?))+)+(?(open)(?!))` | <span>Cortex this is included</span>, <span>Cortex this is included too</span> in not included <span>Cortex this is included</span> not included either <span>Cortex this is included too</span> | -| `(?:subpattern)` | Non-capturing group. Allows the use of parentheses without subpattern being captured into a group. | `Cortex\s(?:include)?` | Cortex include, Cortex  in Cortex include Cortex not include | -| `(?enabled-disabled:subpattern)` | Allows subpattern to be matched with different options than the rest of the pattern. Any inline option characters in enabled or disabled will enable or disable specific options, respectively. To see what inline option characters are available, please check the regular expressions options. | `(?i:c\|v)(ortex)` | cortex, Cortex, Vortex in cortex Cortex Vortex CORTEX | -| `(?=subpattern)` | Zero-width positive look-ahead assertion. Continues matching only if subpattern matches on the right. | `\w+(?=ex\b)` | Cort, Vort in Cortex Vortex Balloon | -| `(?!subpattern)` | Zero-width negative look-ahead assertion. Continues matching only if subpattern does not match on the right. | `\b\w+\.(?!exe)\w+\b` | cortex.jpg, cortex.html in cortex.jpg cortex.html .*.html cortex.exe | -| `(?<=subpattern)` | Zero-width positive look-behind assertion. Continues matching only if subpattern matches on the left. | `(?<=\(\$\))\w+` | variable, 22 in ($)variable ($)22 ($)-- | -| `(?subpattern)` | Prevents backtracking over subpattern, which can improve performance. | `[cv](?>o+r+)` | cor, coor, vor in cortex coortex vortex gortex | +Other `RegexOptions` values (for example compiled or right-to-left matching) are documented on [NET Regular Expression Options][] but are not all exposed as separate block properties in {{% ctx %}}. Prefer [Ordinal][] or [Ordinal Ignore Case][] for machine-oriented matching unless you need linguistic rules — see [Equality][]. -### Back-reference Constructs +Miscellaneous constructs such as inline comments `(?#…)` are described in [Miscellaneous constructs][]. -TODO: +## Remarks -- Include information about any builtin snippets for each regex +### Available on blocks and prompt properties -A back-reference allows a previously matched sub-expression to be identified subsequently in the same regular expression. The following table lists the back-reference constructs supported by regular expressions in .NET. +Set [SearchOptions][] to [Regex][SearchOptions Regex] on supporting Text and Files & Folders blocks. Regex patterns are also used for terminal prompt matching on [Execute SSH Command][] ([SSH TerminalPrompt][]) and [Execute Telnet Command][] ([Telnet TerminalPrompt][]). Regex is not a standalone expression language in the [Expression Editor][] beyond passing pattern strings into those properties. -| Syntax | Description | Pattern | Matches | -|---|---|---|---| -| `\number` | Matches the value of a previously captured group, specified by number. | `\b(\w)\w*\1\b` | xcortex, that in Finds all words like xcortex that start and end with the same letter | -| `\k` | Matches the value of a previously captured named group, specified by name. | `(?\p{P})\w+\k` | !cortex!, ?cortex? in !cortex! ?cortex? XcortexX | +### Overlapping matches -### Alternation Constructs +With [LiteralText][], overlapping matches can be found (for example searching for `"aa"` in `"aaa"` matches at indexes `0` and `1`). With [Regex][SearchOptions Regex], only the first match in that case is returned (`"aa"` at index `0`). The same distinction is documented on Contains, Get Index, Get Folder Content, and Search File blocks. -TODO: +### Comparison Type -- Include information about any builtin snippets for each regex +When a block exposes [Comparison Type][Equality], culture and casing rules still apply to how the regex engine compares text. Prefer [Ordinal][] or [Ordinal Ignore Case][] for machine-oriented patterns unless you intentionally need linguistic rules. See [Equality][]. -Alternation constructs changes a regular expression to enable either/or matching. These constructs include the language elements listed in the following table. +### Search timeout -| Syntax | Description | Pattern | Matches | -|---|---|---|---| -| `\|` | Functions as a logical or. Matches any elements it separates. | `(c\|v)ortex` | cortex, vortex in cortex vortex xortex | -| `(?(subpattern)yes\|no)` | Treats subpattern as a zero-width assertion to check if it matches. If so, attempts to match with the yes subpattern. Otherwise, tries the no subpattern. The \|no part is optional. | `\b(?(\w+tez\b)\w{3}\|cortex)` | cortex, cor in cortex cortez vortex | -| `(?(group)yes\|no)` | Checks if a previously defined group was successfully captured, specified by group, which can be a number or a name for a named group. If so, attempts to match with the yes subpattern. Otherwise, tries the no subpattern. The \|no part is optional. | `(?(\(\$\)))\w+\|'\w+'` | cortex, 'cortex' in ($)cortex 'cortex' | +Supporting blocks apply a match timeout so a pathological pattern cannot run unbound (a common denial-of-service risk with regex). Exact limits vary by block family: -### Substitutions +| Blocks | Typical limit | +| --- | --- | +| [Contains Text][], [Contains Any Text][], [Contains All Text][], [Get Index Of Text][], [Get Folder Content][], [Search File][], [Search Files][] | Often `30` seconds | +| [Find Text][], [Find All Text][], [Find And Replace Text][], [Find And Remove Text][], and related All variants | [BlockTimeout][], or `60` seconds if that is undefined | -TODO: +When the limit is exceeded, the block throws [RegexMatchTimeoutException][]. Check each block's Exceptions table for the exact value. For general guidance, see [.NET regex best practices][]. -- Include information about any builtin snippets for each regex +### Line-by-line file search -Substitutions are regular expression language elements supported in replacement patterns. +[Search File][] and [Search Files][] search each line separately. As a result, the inline single-line option `s` (so that `.` matches `\n`) is not supported for those searches. -| Syntax | Description | Pattern | Replacement | Result | -|---|---|---|---|---| -| `$number` | Substitutes the value of a group, specified by number. | `\b(\w+)(\s)(\w+)\b` | `$3$2$1` | Cortex Great becomes Great Cortex | -| `${name}` | Substitutes the value of a named group, specified by name. | `\b(?\w+)(\s)(?\w+)\b` | `${word2} ${word1}` | Cortex Great becomes Great Cortex | -| `$$` | Substitutes the $ character. | `\b(dollar)` | `$$` | (dollar)name becomes ($)name | -| `$&` | Substitutes the entire match. | `\w+` | `\*\*$&\*\*` | Cortex becomes \*\*Cortex\*\* | -| ``$` `` | Substitutes all input text found before the match. | `#+` | ``$` `` | Co##rtex becomes CoCortex | -| `$'` | Substitutes all input text found after the match. | `#+` | `$'` | Cort##ex becomes Cortexex | -| `$+` | Substitutes the last group captured. | `Co(r)` | `$+` | CoCortex becomes Cortex | -| `$_` | Substitutes the entire input. | `\w+?` | `$_`  | Cortex becomes Cortex Cortex Cortex Cortex Cortex Cortex  | +### Substitutions and Find All outputs -## Remarks +Use [substitution][Substitutions] patterns only in replacement text (for example on [Find And Replace Text][]). [Find All Text][] can return [Match][], [Group][], and [CaptureDetails][] values that expose numbered and named captures from grouping constructs. ### Known Limitations -TODO +* Legacy CORTEX G2 regular expression syntax is not supported. Patterns must use .NET regular expression syntax as described on this page. +* If [Search Options][SearchOptions] is [Regex][SearchOptions Regex] (or [PatternMatching][]) and [Comparison Type][Equality] is [Current Culture][], some character equivalences (for example `æ` and `ae`) may not evaluate as equal — see each text or folder block's remarks (for example [Contains Text][]). +* On [Search File][] and [Search Files][], the inline `s` (single-line) regex option is not supported because matching is line-by-line. ## See Also ### Related Concepts -TODO +* [What is Text?][] — LiteralText, Regex, and PatternMatching overview +* [Pattern Matching Syntax][] — `*` and `?` wildcards +* [Equality][] — Comparison Type and culture/casing rules ### Related Data Types -TODO +* [SearchOptions][] +* [TextToFind][] +* [String][] +* [StringComparison][] +* [Match][] +* [Group][] +* [CaptureDetails][] +* [FileMatch][] ### Related Blocks -TODO: List blocks which support using regex - -TODO: All blocks which support regex should link back here from the properties that support it +* [Contains Text][] +* [Contains Any Text][] +* [Contains All Text][] +* [Find Text][] +* [Find All Text][] +* [Find And Replace Text][] +* [Find And Remove Text][] +* [Get Index Of Text][] +* [Get Folder Content][] +* [Search File][] +* [Search Files][] +* [Execute SSH Command][] +* [Execute Telnet Command][] ### External Documentation -TODO +* [.NET Regular Expressions][NET Regular Expressions] — overview of the .NET regex engine +* [Regular Expression Language - Quick Reference][NET Regex Quick Reference] +* [Options for Regular Expressions][NET Regular Expression Options] +* [Best Practices for Regular Expressions in .NET][.NET regex best practices] +* [Character Classes in Regular Expressions][Character classes] +* [Miscellaneous Constructs in Regular Expressions][Miscellaneous constructs] +* [Example: Scanning for HREFs][Scanning for HREFs] +* [RegexMatchTimeoutException][] + +[Search timeout]: {{< ref "#search-timeout" >}} +[External Documentation]: {{< ref "#external-documentation" >}} +[Regular expression options]: {{< ref "#regular-expression-options" >}} +[Substitutions]: {{< ref "#substitutions" >}} + +[What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} +[Pattern Matching Syntax]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.PatternMatchingSyntax.MainDoc" >}} +[Ordinal]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.Ordinal" >}} +[Ordinal Ignore Case]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.OrdinalIgnoreCase" >}} +[Current Culture]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.ComparisonTypes.CurrentCulture" >}} + +[SearchOptions]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.MainDoc" >}} +[LiteralText]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.LiteralText" >}} +[PatternMatching]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.PatternMatching" >}} +[SearchOptions Regex]: {{< url path="Cortex.Reference.DataTypes.Text.SearchOptions.Regex" >}} +[String]: {{< url path="Cortex.Reference.DataTypes.Text.String.MainDoc" >}} +[StringComparison]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparison.MainDoc" >}} +[TextToFind]: {{< url path="Cortex.Reference.DataTypes.Text.TextToFind.MainDoc" >}} +[Match]: {{< url path="Cortex.Reference.DataTypes.Text.Regex.Match.MainDoc" >}} +[Group]: {{< url path="Cortex.Reference.DataTypes.Text.Regex.Group.MainDoc" >}} +[CaptureDetails]: {{< url path="Cortex.Reference.DataTypes.Text.Regex.CaptureDetails.MainDoc" >}} +[FileMatch]: {{< url path="Cortex.Reference.DataTypes.FilesAndFolders.FileMatch.MainDoc" >}} +[SSH TerminalPrompt]: {{< url path="Cortex.Reference.DataTypes.Ssh.SshSessionDetails.TerminalPrompt" >}} +[Telnet TerminalPrompt]: {{< url path="Cortex.Reference.DataTypes.Telnet.TelnetSessionDetails.TerminalPrompt" >}} + +[Contains Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsText.MainDoc" >}} +[Contains Any Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAnyText.MainDoc" >}} +[Contains All Text]: {{< url path="Cortex.Reference.Blocks.Text.ContainsText.ContainsAllText.MainDoc" >}} +[Find Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindText.MainDoc" >}} +[Find All Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindAllText.MainDoc" >}} +[Find And Replace Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndReplaceText.FindAndReplaceText.MainDoc" >}} +[Find And Remove Text]: {{< url path="Cortex.Reference.Blocks.Text.FindAndRemoveText.FindAndRemoveText.MainDoc" >}} +[Get Index Of Text]: {{< url path="Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText.MainDoc" >}} +[Get Folder Content]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.GetFolderContent.GetFolderContent.MainDoc" >}} +[Search File]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFile.MainDoc" >}} +[Search Files]: {{< url path="Cortex.Reference.Blocks.FilesAndFolders.SearchFile.SearchFiles.MainDoc" >}} +[Execute SSH Command]: {{< url path="Cortex.Reference.Blocks.Ssh.ExecuteSshCommand.ExecuteSshCommandBlock.MainDoc" >}} +[Execute Telnet Command]: {{< url path="Cortex.Reference.Blocks.Telnet.ExecuteTelnetCommand.ExecuteTelnetCommand.MainDoc" >}} + +[Expression Editor]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.ExpressionEditor.MainDoc" >}} +[BlockTimeout]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.CommonProperties.BlockTimeoutProperty" >}} +[RegexParsingFailedException]: {{< url path="Cortex.Reference.Exceptions.Text.Regex.RegexParsingFailedException.MainDoc" >}} + +[Verbatim]: {{< url path="MSDocs.CSharp.Verbatim" >}} +[NET Regular Expressions]: {{< url path="MSDocs.DotNet.BaseTypes.RegularExpressions" >}} +[NET Regex Quick Reference]: {{< url path="MSDocs.DotNet.BaseTypes.RegularExpressionLanguageQuickReference" >}} +[NET Regular Expression Options]: {{< url path="MSDocs.DotNet.BaseTypes.RegularExpressionOptions" >}} +[.NET regex best practices]: {{< url path="MSDocs.DotNet.BaseTypes.RegularExpressionBestPractices" >}} +[Character classes]: {{< url path="MSDocs.DotNet.BaseTypes.CharacterClassesInRegularExpressions.MainDoc" >}} +[Supported Unicode general categories]: {{< url path="MSDocs.DotNet.BaseTypes.CharacterClassesInRegularExpressions.SupportedUnicodeGeneralCategories" >}} +[Supported named blocks]: {{< url path="MSDocs.DotNet.BaseTypes.CharacterClassesInRegularExpressions.SupportedNamedBlocks" >}} +[Miscellaneous constructs]: {{< url path="MSDocs.DotNet.BaseTypes.MiscellaneousConstructsInRegularExpressions" >}} +[Scanning for HREFs]: {{< url path="MSDocs.DotNet.BaseTypes.RegularExpressionExampleScanningForHrefs" >}} +[RegexMatchTimeoutException]: {{< url path="MSDocs.DotNet.Api.System.Text.RegularExpressions.RegexMatchTimeoutException" >}} diff --git a/content/en/docs/2026.3/Reference/data-types/text/stringcomparison.md b/content/en/docs/2026.3/Reference/data-types/text/stringcomparison.md index c162595ef..66f60e8f0 100644 --- a/content/en/docs/2026.3/Reference/data-types/text/stringcomparison.md +++ b/content/en/docs/2026.3/Reference/data-types/text/stringcomparison.md @@ -52,18 +52,20 @@ None ### Related Concepts +- [Equality][] — comparison rules, when to use each `StringComparison` value, and worked examples - [Working With Text][] - [Working With Enums][] ### External Documentation -- [System.StringComparison][StringComparison] +- [System.StringComparison][MS StringComparison] - [System.Enum][] [Working With Text]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.MainDoc" >}} +[Equality]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.Equality.MainDoc" >}} [StringComparer]: {{< url path="Cortex.Reference.DataTypes.Text.StringComparer.MainDoc" >}} -[StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} +[MS StringComparison]: {{< url path="MSDocs.DotNet.Api.System.StringComparison" >}} [Input]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.WhatIsABlockProperty.Input" >}} [Output]: {{< url path="Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.WhatIsABlockProperty.Output" >}} diff --git a/data/urls.toml b/data/urls.toml index f9d8f6bf7..a883fcb06 100644 --- a/data/urls.toml +++ b/data/urls.toml @@ -1487,6 +1487,7 @@ [Cortex.Reference.Blocks.Objects.ConvertObject] [Cortex.Reference.Blocks.Objects.ConvertObject.ConvertObjectToText] MainDoc = "/docs/reference/blocks/objects/convert-object/convert-object-to-text-block-1" + KnownLimitations = "/docs/reference/blocks/objects/convert-object/convert-object-to-text-block-1/#known-limitations" [Cortex.Reference.Blocks.PowerShell] [Cortex.Reference.Blocks.PowerShell.ExecutePowerShellScript] [Cortex.Reference.Blocks.PowerShell.ExecutePowerShellScript.ExecutePowerShellScript] @@ -1552,6 +1553,8 @@ [Cortex.Reference.Blocks.Text.AddText.AddTextBeforeIndex] MainDoc = "/docs/reference/blocks/text/add-text/add-text-before-index-block/" [Cortex.Reference.Blocks.Text.ContainsText] + [Cortex.Reference.Blocks.Text.ContainsText.ContainsText] + MainDoc = "/docs/reference/blocks/text/contains-text/contains-text-block/" [Cortex.Reference.Blocks.Text.ContainsText.ContainsAllText] MainDoc = "/docs/reference/blocks/text/contains-text/contains-all-text-block/" [Cortex.Reference.Blocks.Text.ContainsText.ContainsAnyText] @@ -1622,6 +1625,17 @@ [Cortex.Reference.Blocks.Text.SplitText] [Cortex.Reference.Blocks.Text.SplitText.SplitText] MainDoc = "/docs/reference/blocks/text/split-text/split-text-block/" + [Cortex.Reference.Blocks.Text.IsText] + [Cortex.Reference.Blocks.Text.IsText.IsTextEmpty] + MainDoc = "/docs/reference/blocks/text/is-text/is-text-empty-block/" + [Cortex.Reference.Blocks.Text.IsText.IsTextEmptyOrWhitespace] + MainDoc = "/docs/reference/blocks/text/is-text/is-text-empty-or-whitespace-block/" + [Cortex.Reference.Blocks.Text.IsText.IsTextNull] + MainDoc = "/docs/reference/blocks/text/is-text/is-text-null-block/" + [Cortex.Reference.Blocks.Text.IsText.IsTextNullOrEmpty] + MainDoc = "/docs/reference/blocks/text/is-text/is-text-null-or-empty-block/" + [Cortex.Reference.Blocks.Text.IsText.IsTextNullEmptyOrWhitespace] + MainDoc = "/docs/reference/blocks/text/is-text/is-text-null-empty-or-whitespace-block/" [Cortex.Reference.Blocks.Schedules] [Cortex.Reference.Blocks.Schedules.WaitFor] [Cortex.Reference.Blocks.Schedules.WaitFor.WaitForDuration] @@ -1701,6 +1715,7 @@ MethodExpressions = "/docs/reference/concepts/fundamentals/blocks/block-properties/property-editors/expression-editor/#method-expressions" PropertyExpressions = "/docs/reference/concepts/fundamentals/blocks/block-properties/property-editors/expression-editor/#property-expressions" StringExpressions = "/docs/reference/concepts/fundamentals/blocks/block-properties/property-editors/expression-editor/#string-expressions" + VerbatimStrings = "/docs/reference/concepts/fundamentals/blocks/block-properties/property-editors/expression-editor/#verbatim-strings" [Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.LiteralEditor] MainDoc = "/docs/reference/concepts/fundamentals/blocks/block-properties/property-editors/literal-editor/" [Cortex.Reference.Concepts.Fundamentals.Blocks.BlockProperties.PropertyEditors.VariableEditor] @@ -2322,6 +2337,7 @@ Base64 = "/docs/reference/data-types/text/textencodingformat/#base64" Hex = "/docs/reference/data-types/text/textencodingformat/#hex" Html = "/docs/reference/data-types/text/textencodingformat/#html" + Utf8 = "/docs/reference/data-types/text/textencodingformat/#utf8" Base64Url = "/docs/reference/data-types/text/textencodingformat/#base64url" Url = "/docs/reference/data-types/text/textencodingformat/#url" [Cortex.Reference.DataTypes.Text.EncryptableText] @@ -2958,6 +2974,7 @@ UnauthorizedAccessException = "https://learn.microsoft.com/en-us/dotnet/api/system.unauthorizedaccessexception" [MSDocs.DotNet.Api.System.Char] MainDoc = "https://learn.microsoft.com/en-us/dotnet/api/system.char" + IsWhiteSpace = "https://learn.microsoft.com/en-us/dotnet/api/system.char.iswhitespace" [MSDocs.DotNet.Api.System.Collections] IEnumerable = "https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerable" [MSDocs.DotNet.Api.System.Collections.Generic] @@ -3070,6 +3087,8 @@ FileNotFoundException = "https://learn.microsoft.com/en-us/dotnet/api/system.io.filenotfoundexception" IOException = "https://learn.microsoft.com/en-us/dotnet/api/system.io.ioexception" PathTooLongException = "https://learn.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception" + [MSDocs.DotNet.Api.System.IO.DirectoryInfo] + GetFiles = "https://learn.microsoft.com/en-us/dotnet/api/system.io.directoryinfo.getfiles" [MSDocs.DotNet.Api.System.Object] MainDoc = "https://learn.microsoft.com/en-us/dotnet/api/system.object" ReferenceEquals = "https://learn.microsoft.com/en-us/dotnet/api/system.object.referenceequals" @@ -3092,9 +3111,12 @@ ConcatGuide = "https://learn.microsoft.com/en-us/dotnet/csharp/how-to/concatenate-multiple-strings" ConcatOperator = "https://learn.microsoft.com/en-us/dotnet/csharp/how-to/concatenate-multiple-strings#-and--operators" StringInterpolation = "https://learn.microsoft.com/en-us/dotnet/csharp/how-to/concatenate-multiple-strings#string-interpolation" + Empty = "https://learn.microsoft.com/en-us/dotnet/api/system.string.empty" Format = "https://learn.microsoft.com/en-us/dotnet/api/system.string.format" IndexOf = "https://learn.microsoft.com/en-us/dotnet/api/system.string.indexof" Insert = "https://learn.microsoft.com/en-us/dotnet/api/system.string.insert" + IsNullOrEmpty = "https://learn.microsoft.com/en-us/dotnet/api/system.string.isnullorempty" + IsNullOrWhiteSpace = "https://learn.microsoft.com/en-us/dotnet/api/system.string.isnullorwhitespace" Join = "https://learn.microsoft.com/en-us/dotnet/api/system.string.join" LastIndexOf = "https://learn.microsoft.com/en-us/dotnet/api/system.string.lastindexof" [MSDocs.DotNet.Api.System.TimeSpan] @@ -3125,10 +3147,25 @@ BasicStringOperations = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/basic-string-operations" BestPracticesStrings = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings" CharacterEncodingIntroduction = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding-introduction" + CustomNumericFormatStrings = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings" ParsingNumeric = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/parsing-numeric" + StandardNumericFormatStrings = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings" + RegularExpressions = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expressions" + RegularExpressionLanguageQuickReference = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference" + RegularExpressionOptions = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options" + RegularExpressionBestPractices = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices" + MiscellaneousConstructsInRegularExpressions = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/miscellaneous-constructs-in-regular-expressions" + RegularExpressionExampleScanningForHrefs = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-example-scanning-for-hrefs" + [MSDocs.DotNet.BaseTypes.CharacterClassesInRegularExpressions] + MainDoc = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions" + SupportedUnicodeGeneralCategories = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#SupportedUnicodeGeneralCategories" + SupportedNamedBlocks = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#supported-named-blocks" [MSDocs.DotNet.BaseTypes.ChangingCase] MainDoc = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case" CompareStringsOfMixedCase = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/changing-case#compare-strings-of-mixed-case" + [MSDocs.DotNet.BaseTypes.CompositeFormatting] + MainDoc = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting" + EscapingBraces = "https://learn.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting#escaping-braces" [MSDocs.DotNet.Core] [MSDocs.DotNet.Core.Extensions] CultureInsensitiveCaseChanges = "https://learn.microsoft.com/en-us/dotnet/core/extensions/performing-culture-insensitive-case-changes" From 7918206db66ee19b757ff4ef05cbcd609fe3003c Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Wed, 5 Aug 2026 14:31:45 +0100 Subject: [PATCH 12/14] changes following PO Review --- .../Concepts/working-with/text/empty-text-and-whitespace.md | 3 +-- .../Concepts/working-with/text/pattern-matching-syntax.md | 2 +- .../Reference/Concepts/working-with/text/what-is-text.md | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md index e58e66605..87d0b1c01 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md @@ -24,8 +24,7 @@ In {{% ctx %}}, a [String][] can be missing (`null`), empty (length `0`), or con | Test for empty only (`""`) | [Is Text Empty][] or `($)Text == ""` / `($)Text.Length == 0` | | Test for `null` or empty | [Is Text Null Or Empty][] or [String.IsNullOrEmpty][] | | Test for empty or whitespace-only (space, tab, CR, LF) | [Is Text Empty Or Whitespace][] | -| Test for `null`, empty, or whitespace-only (space, tab, CR, LF) | [Is Text Null, Empty Or Whitespace][] | -| Test for `null`, empty, or whitespace-only (.NET set) | [String.IsNullOrWhiteSpace][] | +| Test for `null`, empty, or whitespace-only (space, tab, CR, LF) | [Is Text Null, Empty Or Whitespace][] or [String.IsNullOrWhiteSpace][]| Treat `null`, empty, and whitespace as distinct cases when validating input. For an overview of text in {{% ctx %}}, see [What is Text?][]. diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md index 588468cb7..fbc767e9f 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/pattern-matching-syntax.md @@ -64,7 +64,7 @@ Common extensions (`.txt`, `.log`, `.csv`, `.json`, `.xml`, `.pdf`, `.docx`, `.x | Language | Exact characters | `*` and `?` only | Full .NET regex | | Typical use | Fixed phrases | File-style or simple fuzzy text patterns | Complex structure, groups, alternation | | Invalid pattern errors | N/A | N/A (wildcards only) | Invalid patterns can throw [RegexParsingFailedException][] | -| Search timeout | Not applied as for PatternMatching/Regex on many Contains blocks | Can throw [RegexMatchTimeoutException][] if the search exceeds the block's documented limit (often `30` seconds) | Same timeout behaviour on those blocks | +| Search timeout | Not applied as for PatternMatching/Regex on many Contains blocks | Can throw [RegexMatchTimeoutException][] if the search exceeds the block's documented limit (often `30` seconds) | Same timeout behaviour as PatternMatching. | "Contains" is a **block operation** (does this text contain a match?). [LiteralText][], [PatternMatching][], and [Regex][SearchOptions Regex] are **how** the search text is interpreted via [SearchOptions][]. diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md index 90665e33f..9142cf976 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md @@ -146,7 +146,7 @@ Strings use **zero-based** indexes. Common operations: | Slice by range | `Substring`, ranges/indexes where supported | [Get Text Between Indexes][], [Get Text at Beginning][], [Get Text at End][] | | Length | `"Cortex".Length` → `6` | Related Get Length blocks under [Text][Text Blocks] | -Indexes refer to [Char][] units (UTF-16 code units), not always to user-perceived graphemes. For ranges and index syntax in modern C#, see [Indices and ranges][]. For broader string APIs, see [Basic string operations][]. +For ranges and index syntax in modern C#, see [Indices and ranges][]. For broader string APIs, see [Basic string operations][]. ## Remarks From ecbcc2cdc104d08daead54e8141743fbd19dfc49 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Wed, 5 Aug 2026 16:27:29 +0100 Subject: [PATCH 13/14] Final changes following PO Review --- .../text/empty-text-and-whitespace.md | 53 ++++++++----------- .../working-with/text/regex-syntax.md | 5 -- .../working-with/text/what-is-text.md | 8 +-- data/urls.toml | 3 ++ 4 files changed, 29 insertions(+), 40 deletions(-) diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md index 87d0b1c01..ed947528f 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/empty-text-and-whitespace.md @@ -14,7 +14,7 @@ In {{% ctx %}}, a [String][] can be missing (`null`), empty (length `0`), or con | --- | --- | --- | --- | | `null` | Unassigned [String][], or explicitly `null` | — | No string instance. See [Null and Nullable Types][]. | | Empty text | `""` or [String.Empty][] | `0` | A valid string with no characters. | -| Whitespace-only | `" "`, `"\t"`, `"\r\n"` | Greater than `0` | Not empty; every character is space, tab, CR, or LF (Is Text blocks). | +| Whitespace-only | `" "`, `"\t"`, `"\r\n"` | Greater than `0` | Not empty; every character is whitespace ([Char.IsWhiteSpace][]). | | Non-empty text | `"Cortex"`, `" a "` | Greater than `0` | Contains at least one non-whitespace character (the last example also contains spaces). | | Goal | Prefer | @@ -23,8 +23,8 @@ In {{% ctx %}}, a [String][] can be missing (`null`), empty (length `0`), or con | Test for `null` only | [Is Text Null][] or `($)Text == null` | | Test for empty only (`""`) | [Is Text Empty][] or `($)Text == ""` / `($)Text.Length == 0` | | Test for `null` or empty | [Is Text Null Or Empty][] or [String.IsNullOrEmpty][] | -| Test for empty or whitespace-only (space, tab, CR, LF) | [Is Text Empty Or Whitespace][] | -| Test for `null`, empty, or whitespace-only (space, tab, CR, LF) | [Is Text Null, Empty Or Whitespace][] or [String.IsNullOrWhiteSpace][]| +| Test for empty or whitespace-only | [Is Text Empty Or Whitespace][] | +| Test for `null`, empty, or whitespace-only | [Is Text Null, Empty Or Whitespace][] or [String.IsNullOrWhiteSpace][] | Treat `null`, empty, and whitespace as distinct cases when validating input. For an overview of text in {{% ctx %}}, see [What is Text?][]. @@ -63,7 +63,13 @@ In the [Literal Editor][], surrounding quotes are not required for [String][] pr ## Whitespace -In {{% ctx %}} Text blocks that check for whitespace (for example [Is Text Empty Or Whitespace][] and [Is Text Null, Empty Or Whitespace][]), **whitespace** means any of these characters: +**Whitespace** is any [Char][] that [Char.IsWhiteSpace][] categorizes as white space. {{% ctx %}} uses that same .NET definition in Text blocks that check for whitespace (for example [Is Text Empty Or Whitespace][] and [Is Text Null, Empty Or Whitespace][]) and in expressions such as [String.IsNullOrWhiteSpace][]. When rendered, these characters typically occupy space on the page but do not show a glyph. + +A string is **whitespace-only** when its length is greater than `0` and every character is whitespace. Whitespace-only text is **not** empty: `" ".Length` is `3`, not `0`. + +### Common whitespace characters + +The table lists characters often used in flows. Escape sequences apply in regular (non-verbatim) string literals in the [Expression Editor][] — see [String escape sequences][]. Block documentation often cites these as examples; they are not the full set. | Character | Escape / literal | Unicode | Notes | | --- | --- | --- | --- | @@ -71,31 +77,20 @@ In {{% ctx %}} Text blocks that check for whitespace (for example [Is Text Empty | Character tabulation (tab) | `"\t"` | U+0009 | Horizontal tab | | Line feed (newline) | `"\n"` | U+000A | Unix-style line ending component | | Carriage return | `"\r"` | U+000D | Often combined with `\n` as `"\r\n"` | +| Vertical tab | `"\v"` | U+000B | Less common in business text | +| Form feed | `"\f"` | U+000C | Less common in business text | -Escape sequences apply in regular (non-verbatim) string literals in the [Expression Editor][] — see [String escape sequences][]. - -A string is **whitespace-only** when its length is greater than `0` and every character is one of the whitespace characters above. Whitespace-only text is **not** empty: `" ".Length` is `3`, not `0`. - -### .NET whitespace (expressions) - -In expressions, [Char.IsWhiteSpace][] and [String.IsNullOrWhiteSpace][] use a **broader** Unicode set than the Is Text whitespace blocks. That set includes the four characters above plus others such as no-break space (U+00A0), vertical tab (U+000B), form feed (U+000C), next line (U+0085), line separator (U+2028), paragraph separator (U+2029), and further space-separator code points. See [Char.IsWhiteSpace][]. - -| Approach | Whitespace characters | -| --- | --- | -| [Is Text Empty Or Whitespace][], [Is Text Null, Empty Or Whitespace][] | Space, tab, carriage return, line feed only | -| [String.IsNullOrWhiteSpace][], [Char.IsWhiteSpace][] | Full .NET whitespace set | - -Use the Is Text blocks when you want the product definition (the four characters). Use [String.IsNullOrWhiteSpace][] or [Char.IsWhiteSpace][] in the [Expression Editor][] only when you need the broader .NET behaviour. +[Char.IsWhiteSpace][] also returns `true` for other Unicode whitespace characters (for example no-break space U+00A0, next line U+0085, line separator U+2028, paragraph separator U+2029, and further space-separator code points). For the full list, see [Char.IsWhiteSpace][]. ### Examples -| Text | Empty? | Whitespace-only? (blocks) | Notes | +| Text | Empty? | Whitespace-only? | Notes | | --- | --- | --- | --- | | `""` | Yes | No | Length `0` | | `" "` | No | Yes | Spaces only | | `"\t\r\n"` | No | Yes | Tab and line endings only | +| `"\u00A0"` (no-break space) | No | Yes | Included by [Char.IsWhiteSpace][] | | `" a "` | No | No | Contains a non-whitespace character | -| `"\u00A0"` (no-break space) | No | No | Not whitespace for Is Text blocks; is whitespace for [Char.IsWhiteSpace][] | | `null` | No | No | Not a string instance | ## Checking null, empty, and whitespace @@ -109,8 +104,8 @@ Use the Is Text blocks when you want a clear yes/no result in a flow: | [Is Text Null][] | `null` | | [Is Text Empty][] | empty (`""`) | | [Is Text Null Or Empty][] | `null` or empty | -| [Is Text Empty Or Whitespace][] | empty or whitespace-only (space, tab, CR, LF) | -| [Is Text Null, Empty Or Whitespace][] | `null`, empty, or whitespace-only (space, tab, CR, LF) | +| [Is Text Empty Or Whitespace][] | empty or whitespace-only | +| [Is Text Null, Empty Or Whitespace][] | `null`, empty, or whitespace-only | Each block's remarks document how `null` and whitespace-only values are treated. For example, [Is Text Empty][] returns `false` for both `null` and `" "`. @@ -121,10 +116,10 @@ Each block's remarks document how `null` and whitespace-only values are treated. | Is `null`? | `($)Text == null` | | Is empty? | `($)Text == ""` or `($)Text == String.Empty` | | Is `null` or empty? | `string.IsNullOrEmpty(($)Text)` | -| Is `null`, empty, or whitespace-only (.NET set)? | `string.IsNullOrWhiteSpace(($)Text)` | -| Character is whitespace (.NET set)? | `char.IsWhiteSpace(($)Text[0])` | +| Is `null`, empty, or whitespace-only? | `string.IsNullOrWhiteSpace(($)Text)` | +| Character is whitespace? | `char.IsWhiteSpace(($)Text[0])` | -[String.IsNullOrEmpty][] matches [Is Text Null Or Empty][]. [String.IsNullOrWhiteSpace][] is **not** an exact equivalent of [Is Text Null, Empty Or Whitespace][] because .NET recognizes additional whitespace characters — see [Whitespace characters in Is Text blocks][]. Prefer blocks when the decision should be visible on the flow canvas and you want the product whitespace definition; use expressions inside other property values or more complex conditions. +[String.IsNullOrEmpty][] and [String.IsNullOrWhiteSpace][] are the usual expression equivalents of the combined Is Text checks. Prefer blocks when the decision should be visible on the flow canvas; use expressions inside other property values or more complex conditions. ## Remarks @@ -134,7 +129,7 @@ Each block's remarks document how `null` and whitespace-only values are treated. ### Empty versus whitespace-only text -Whitespace-only text has length greater than `0`, so it is not empty. Blocks and APIs that test only for empty (for example [Is Text Empty][] or [String.IsNullOrEmpty][]) return `false` for `" "`. Use [Is Text Empty Or Whitespace][] or [Is Text Null, Empty Or Whitespace][] when whitespace-only input (space, tab, CR, or LF) should be treated like missing content. +Whitespace-only text has length greater than `0`, so it is not empty. Blocks and APIs that test only for empty (for example [Is Text Empty][] or [String.IsNullOrEmpty][]) return `false` for `" "`. Use [Is Text Empty Or Whitespace][], [Is Text Null, Empty Or Whitespace][], or [String.IsNullOrWhiteSpace][] when whitespace-only input should be treated like missing content. ### Empty versus no value @@ -144,10 +139,6 @@ Leaving a property with **no value** is not the same as empty text. On [Set Vari When a flow needs a defined empty string (for concatenation, default messages, or APIs that reject `null`), assign `""` or [String.Empty][] rather than leaving the value unset. Reserve `null` for “no value provided.” -### Whitespace characters in Is Text blocks - -[Is Text Empty Or Whitespace][] and [Is Text Null, Empty Or Whitespace][] treat only space, tab, carriage return, and line feed as whitespace. Other Unicode whitespace characters (for example no-break space U+00A0) are **not** treated as whitespace by those blocks. [String.IsNullOrWhiteSpace][] and [Char.IsWhiteSpace][] in expressions use the broader .NET set — see [.NET whitespace (expressions)][]. - ## See Also ### Related Concepts @@ -181,8 +172,6 @@ When a flow needs a defined empty string (for concatenation, default messages, o * [System.Char][] [Empty versus no value]: {{< ref "#empty-versus-no-value" >}} -[Whitespace characters in Is Text blocks]: {{< ref "#whitespace" >}} -[.NET whitespace (expressions)]: {{< ref "#net-whitespace-expressions" >}} [What is Text?]: {{< url path="Cortex.Reference.Concepts.WorkingWith.Text.WhatIsText.MainDoc" >}} [Null and Nullable Types]: {{< url path="Cortex.Reference.Concepts.Fundamentals.DataTypes.NullAndNullableTypes.MainDoc" >}} diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md index 87fe0abd3..48338917e 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/regex-syntax.md @@ -211,15 +211,10 @@ When the limit is exceeded, the block throws [RegexMatchTimeoutException][]. Che [Search File][] and [Search Files][] search each line separately. As a result, the inline single-line option `s` (so that `.` matches `\n`) is not supported for those searches. -### Substitutions and Find All outputs - -Use [substitution][Substitutions] patterns only in replacement text (for example on [Find And Replace Text][]). [Find All Text][] can return [Match][], [Group][], and [CaptureDetails][] values that expose numbered and named captures from grouping constructs. - ### Known Limitations * Legacy CORTEX G2 regular expression syntax is not supported. Patterns must use .NET regular expression syntax as described on this page. * If [Search Options][SearchOptions] is [Regex][SearchOptions Regex] (or [PatternMatching][]) and [Comparison Type][Equality] is [Current Culture][], some character equivalences (for example `æ` and `ae`) may not evaluate as equal — see each text or folder block's remarks (for example [Contains Text][]). -* On [Search File][] and [Search Files][], the inline `s` (single-line) regex option is not supported because matching is line-by-line. ## See Also diff --git a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md index 9142cf976..076cc1d25 100644 --- a/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md +++ b/content/en/docs/2026.3/Reference/Concepts/working-with/text/what-is-text.md @@ -142,9 +142,9 @@ Strings use **zero-based** indexes. Common operations: | Need | Expression examples | Blocks | | --- | --- | --- | | Character at index | `"Cortex"[0]` → `'C'` | — | -| Find position | `"Cortex".IndexOf("tex")` | [Get Index of Text][] | -| Slice by range | `Substring`, ranges/indexes where supported | [Get Text Between Indexes][], [Get Text at Beginning][], [Get Text at End][] | -| Length | `"Cortex".Length` → `6` | Related Get Length blocks under [Text][Text Blocks] | +| Find position | `"Cortex".IndexOf("tex")` → `3` | [Get Index of Text][] | +| Slice by range | `"Cortex".Substring(2, 3)` → `"rte"`; `"Cortex"[2..5]` → `"rte"` | [Get Text Between Indexes][], [Get Text at Beginning][], [Get Text at End][] | +| Length | `"Cortex".Length` → `6` | [Get Length][] | For ranges and index syntax in modern C#, see [Indices and ranges][]. For broader string APIs, see [Basic string operations][]. @@ -189,6 +189,7 @@ None * [Contains All Text][] / [Contains Any Text][] * [Find Text][] / [Find All Text][] * [Get Index of Text][] +* [Get Length][] * [Get Text Between Indexes][] / [Get Text at Beginning][] / [Get Text at End][] * [Add Text at Beginning][] * [Join Text][] @@ -266,6 +267,7 @@ None [Find Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindText.MainDoc" >}} [Find All Text]: {{< url path="Cortex.Reference.Blocks.Text.FindText.FindAllText.MainDoc" >}} [Get Index of Text]: {{< url path="Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText.MainDoc" >}} +[Get Length]: {{< url path="Cortex.Reference.Blocks.Text.GetLength.GetLength.MainDoc" >}} [Get Text Between Indexes]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextBetweenIndexes.MainDoc" >}} [Get Text at Beginning]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextAtBeginning.MainDoc" >}} [Get Text at End]: {{< url path="Cortex.Reference.Blocks.Text.GetText.GetTextAtEnd.MainDoc" >}} diff --git a/data/urls.toml b/data/urls.toml index a883fcb06..29a69e0be 100644 --- a/data/urls.toml +++ b/data/urls.toml @@ -1597,6 +1597,9 @@ [Cortex.Reference.Blocks.Text.GetIndex] [Cortex.Reference.Blocks.Text.GetIndex.GetIndexOfText] MainDoc = "/docs/reference/blocks/text/get-index/get-index-of-text-block/" + [Cortex.Reference.Blocks.Text.GetLength] + [Cortex.Reference.Blocks.Text.GetLength.GetLength] + MainDoc = "/docs/reference/blocks/text/get-length/get-length-block/" [Cortex.Reference.Blocks.Text.GetText] [Cortex.Reference.Blocks.Text.GetText.GetTextAtBeginning] MainDoc = "/docs/reference/blocks/text/get-text/get-text-at-beginning-block/" From 30d19577561968be234f3fdd7bab15d8a976d2f2 Mon Sep 17 00:00:00 2001 From: Donna-Marie Smith Date: Wed, 5 Aug 2026 18:51:57 +0100 Subject: [PATCH 14/14] removed youtube from link check --- .htmltest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.htmltest.yml b/.htmltest.yml index a785b2beb..39bd6ce27 100644 --- a/.htmltest.yml +++ b/.htmltest.yml @@ -36,6 +36,7 @@ IgnoreURLs: - "https://letsencrypt.org/.*" - "https://support.microsoft.com/.*" - "https://www.iso.org/.*" +- "https://www.youtube.com/.*" IgnoreDirs: - "docs/?.*/_print/" - "docs/?.*/_shared/"