# Carbone > Carbone is an efficient, no-code, universal document generation and conversion engine and API. It merges JSON data with a template designed in your usual editor (Word, Excel, PowerPoint, LibreOffice, Google Docs, OnlyOffice, HTML, or Markdown) and renders the result in 100+ output formats. Design is separated from data, so non-developers can author templates and automate workflows while developers and AI assistants integrate programmatically. Output is deterministic: the same template plus the same data always yields the same document. Documentation is currently English-only. Key facts: - Input template formats: PDF, DOCX, XLSX, PPTX, ODT, ODS, ODP, ODG, XML, HTML, Markdown, SVG, IDML - Output formats: 100+ total, including PDF, DOCX, XLSX, PPTX, ODT, ODS, ODP, CSV, HTML, Markdown, TXT, XML, IDML, JPG, PNG, EPUB - Template editors: design in LibreOffice, Microsoft Word / Excel / PowerPoint, Google Docs, Apple Pages, or OnlyOffice — no proprietary editor and no code-only template syntax - AI integration: install the Carbone Skill to teach the templating language to any assistant that supports the open Agent Skills standard (https://agentskills.io), or wire the Carbone MCP server into any MCP client to expose Carbone's full API. See the "AI integration" section below for details and the supported tools. - Rich content inside templates: dynamic charts, tables, barcodes, QR codes, images, colors, inline SVGs, hyperlinks, HTML from WYSIWYG editors, conditional rendering, loops, and chainable formatters - Tag syntax: `{d.field}` data, `{c.field}` complement, `{#alias}`/`{$alias}` aliases, `{t(key)}` i18n, `{o.option}` options; formatters chain with `:` - Multilingual rendering with locale-aware numbers, dates, times, and currency formats - Batch generation of hundreds of documents per request, with template versioning via `deployedAt` timestamps - E-signature workflows: render the document with Carbone, then hand off to a signature provider for the signature step (see the Integrations section below for the supported providers) - PDF utilities: merge multiple PDFs into a single document, convert HTML to PDF (drop-in alternative to Gotenberg or Puppeteer), convert Markdown to PDF, convert Office documents to PDF - Deployment options: - Carbone Cloud — managed REST API, EU-hosted in Paris - Carbone On-Premise — self-hosted via the Docker image `carbone/carbone-ee` or via native server binaries for Linux, macOS, and Windows (all binary downloads listed on the Carbone changelog page, https://carbone.io/changelog.md) - Carbone On-AWS — turnkey AWS deployment, all Enterprise features enabled by default - Embedded Carbone JS — open-source Node.js library - Licensing for self-hosted deployments: - Both the Docker image and the OS binaries start for free with no license, granting access to all Community features - Providing an Enterprise license unlocks all features (advanced features, batch generation, conversion engines, support) - Carbone On-AWS includes the Enterprise license by default - For an Enterprise license, contact the Carbone commercial team at contact@carbone.io - Support: support@carbone.io — answered directly by Carbone's engineers and founders, not by bots or an outsourced helpdesk, so questions and bug reports get a real human reply quickly ## Root URL - https://account.carbone.io - Carbone Cloud account and API key management - https://api.carbone.io - REST API base URL (Carbone Cloud) - https://studio.carbone.io - Carbone Studio (template designer) - https://mcp.carbone.io - Hosted Carbone MCP server - https://hub.docker.com/r/carbone/carbone-ee - Docker Hub image for self-hosting the Carbone Server API (On-Premise) - https://github.com/carboneio - Open-source repositories (core engine, MCP, Skill, SDKs, integrations, examples) - https://carbone.io/llms-full.txt - Full content of this index, with each page inlined ## AI integration Two independent layers, useful alone or together. - **Carbone Skill — knowledge layer.** Teaches the templating language (tags, formatters, loops, conditions, `:set`, HTML and Markdown). Available for any assistant that supports the open Agent Skills standard (https://agentskills.io): Claude (Desktop, Code, claude.ai), ChatGPT, Google Gemini, Microsoft Copilot, Cursor, OpenAI Apps SDK. [Docs](https://carbone.io/documentation/developer/ai/skills.md). - **Carbone MCP server — action layer.** Official Model Context Protocol server exposing Carbone's full API. Hosted at https://mcp.carbone.io, or self-hosted via the npm `carbone-mcp` package (stdio) or Docker (HTTP). Tools: render_document, convert_document, upload_template, list_templates, download_template, update_template_metadata, delete_template, list_categories, list_tags, get_api_status, get_capabilities. Compatible with Claude Desktop, Claude Code, Cursor, VS Code, Microsoft Copilot Studio, Mistral Vibe, and any MCP-aware agent. [Docs](https://carbone.io/documentation/developer/ai/mcp.md). ## Integrations at a glance - AI assistants: Claude, ChatGPT, Google Gemini, Microsoft Copilot, Meta Llama, DeepSeek, Mistral Vibe (formerly Le Chat) - Automation / no-code: n8n, Make (Integromat), Zapier, Glide, Retool, Ninox - CRM & business apps: Salesforce, HubSpot, Pipedrive, Odoo (official `report_carbone` module, AGPL-3, for Odoo 18.0 and 19.0) - Electronic signature: DocuSign, Subnoto, Yousign, Docuseal, Documenso, SignWell - Languages & SDKs: Node.js, JavaScript, Python, PHP, Go, Java - Infrastructure: Docker (Carbone Server API self-hosted), Amazon Web Services ## Getting Started #### [Generate a document](https://carbone.io/documentation/quickstart/getting-started/generate-a-document.md) Generate your first report in just a few minutes with Carbone! ##### 1 - Create Your Template Generating your first document with Carbone is very simple. - **Open Microsoft Word, LibreOffice, or Google Docs** (start with ODT or DOCX. See [all supported formats](/documentation/design/overview/template-feature.md#template-types)). - **Add Carbone tags** where you want data to appear. For example: ```text Name: {d.firstname} {d.lastname} ``` ![Carbone first report](/img/doc/first-template.webp) Bravo 🤩, your template is ready to use! You can also use other editors to create templates, such as VSCode, Cursor.sh, Sublime Text (for XML/HTML/MD templates), Affinity Designer or Adobe Illustrator (for SVG), TinyMCE, CKEditor, or WordPress editor (for HTML). ##### 2 - Upload to Carbone Studio - [Sign up](https://account.carbone.io/login?register=true) for your Carbone Cloud account. - Open [Carbone Studio](https://studio.carbone.io). - Click **"Start from Scratch"**, upload your template, and add your JSON data. ![First upload on Carbone Studio](/img/doc/quickstart-first-upload.gif) ##### 3 - Preview and Save - See your document update instantly as you edit the data or the template. - When happy, save your template. - The `Template ID` lets you use this template with Carbone APIs. See the [Quickstart for developers](/documentation/quickstart/integration/for-developers.md) for more info. ![First render on Carbone Studio](/img/doc/quickstart-first-render.gif) ##### 4 - Download Your Document Download your report in any format you need. ![Save your first report](/img/doc/quickstart-save-report.gif) ##### Next steps - Learn to [design template](/documentation/design/overview/getting-started.md) - [Integrate Carbone](/documentation/quickstart/integration/for-developers.md) to automate your document generation. ##### Amazing feature - Live reloading Live reloading allows designing and editing templates from your local text editor, and Carbone Studio will preview the report automatically. Upload a template once, and the document previews on Carbone studio as soon as the JSON/template is updated. In a nutshell, you create a new document, for example, in Microsoft Word. Once uploaded to Carbone Studio, you can edit your template in Word, and each time you save, the preview will be refreshed in Studio. It's magic 🎉 --- #### [For Developers](https://carbone.io/documentation/quickstart/integration/for-developers.md) Add document generation into your application ##### Quick API Integration Guide Get started with document generation in just a few minutes! For detailed options, see the [full HTTP API documentation](/documentation/developer/http-api/introduction.md). ##### 1\. Get Your API Key - Create a free [Carbone Account](https://account.carbone.io). - Go to your dashboard to get your API keys: - **Production API key**: For production use. [Get 100 free documents/month](https://account.carbone.io/plans?st=0&idp=355329978948510477) after adding a payment method. - **Test API key**: Always free. For development and testing (adds a Carbone watermark, PDF export only). ![Get API Keys from Carbone Account](/img/doc/carbone-account-api-keys.png) - Use your key in all API requests: ```json "authorization": "Bearer YOUR_API_TOKEN" ``` ##### 2\. Get a Template ID To generate documents, you need a template `Id`. There are two easy ways to get it: **Copy the ID from Carbone Studio** If you uploaded [your template](/documentation/quickstart/getting-started/generate-a-document.md) via [Carbone Studio](https://studio.carbone.io), simply open your template and copy its ID from the Studio interface. ![Get templateID from Studio](/img/doc/studio-get-templateid.png) **Or Upload your template via the API** If you prefer to work programmatically, you can [upload your template](/documentation/developer/http-api/manage-templates.md#upload-a-template) file using the API. Here’s a curl sample command: ```bash export CARBONE_API_TOKEN="" export ABSOLUTE_FILE_PATH="/path/to/first-template.docx" curl --location --request POST 'https://api.carbone.io/template' \ --header 'carbone-version: 5' \ --header 'Expect:' \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer '${CARBONE_API_TOKEN} \ --form 'versioning=true' \ --form 'template=@'${ABSOLUTE_FILE_PATH} ``` Set `versioning=true` to use the new `id`/`versionId` fields from the v5 API: ```js { success: true, data: { id: "424242424242", // template id versionId: "af42af42af42af42af42af42af42af42af42af42af42af42af42af42af42af42", // unique hash type: "docx", size: 12012, createdAt: 21545451, // For backward compatibility with v4 API, deprecated fields are still returned: // "templateId" (now "versionId" in v5) templateId: "af42af42af42af42af42af42af42af42af42af42af42af42af42af42af42af42", // "templateExtension" (now "type") templateExtension: "docx" } } ``` With either method, you now have your template `id` or `versionId` (both are accepted) ready to use in API calls! ##### 3\. Generate Your Document Replace `TEMPLATE_ID_OR_VERSION_ID` with your template’s ID. ```bash curl --location --request POST 'https://api.carbone.io/render/TEMPLATE_ID_OR_VERSION_ID?download=true' \ --header 'carbone-version: 5' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer '${CARBONE_API_TOKEN} \ --data-raw '{ "data": { "name": "John" }, "convertTo": "pdf" }' \ -o 'report.pdf' ``` Your generated document will be saved as `report.pdf`. ##### Prefer SDKs? Carbone provides ready-to-use SDKs to speed up development: - [NodeJS](/documentation/developer/api-sdks/nodejs.md) - [Javascript](/documentation/developer/api-sdks/javascript.md) - [Go](/documentation/developer/api-sdks/go.md) - [PHP](/documentation/developer/api-sdks/php.md) - [Python](/documentation/developer/api-sdks/python.md) - [Rust](/documentation/developer/api-sdks/rust.md) - [Java](/documentation/developer/api-sdks/java.md) That’s it! 🚀 Now you’re ready to automate document generation with Carbone. --- #### [For NoCode makers](https://carbone.io/documentation/quickstart/integration/for-nocode-makers.md) Add document generation into your environment ##### Integrate with Nocode Tool Carbone's philosophy is based on a NoCode/LowCode approach. You can therefore use our integrations on the following platforms: - [Zappier](https://zapier.com/apps/carboneio/integrations) - [Bubble](https://bubble.io/plugin/report-generator---carboneio-1643965604424x905182936531206100) - [Make](https://www.make.com/en/integrations/carbone) - [Airtable](https://airtable.com/marketplace/blkDYrh9J4DbPp5Jq/document-generator-carbone) - [N8N](https://n8n.io/integrations/carbone/) - Microsoft Power Platform We're working on expanding this list, so don't hesitate to check our [Integration pages](/integration/index.md) for the full list. It is also possible to integrate Carbone directly with [HTTP APIs](/documentation/developer/http-api/introduction.md). Here's an example with [Retool](https://medium.com/p/47ea82b13a3e). ##### For CRM Integrators or CRM users Carbone also integrates seamlessly with CRM solutions on the market using our [HTTP APIs](/documentation/developer/http-api/introduction.md). \[COMING SOON\] We are working on developing dedicated connectors for the following platforms: - Hubspot - Salesforce - Odoo - Monday --- #### [For DevOps](https://carbone.io/documentation/quickstart/integration/for-devops.md) Add document generation to your environment ##### Carbone Cloud Using Carbone Cloud is the easiest way to harness the power of our document generator for your projects. All you have to do is create an account, subscribe to a package based on your needs and use the API. There are no architecture, maintenance, or hosting costs. The service is hosted in France and complies with the General Data Protection Regulation (GDPR). You can read our [architecture guide](/documentation/developer/under-the-hood/architecture-guideline.md) for full implementation details. ##### Carbone Self-hosted Our “On-Premise” solution is available if you wish to host one or more instances of Carbone yourself. It integrates seamlessly into any environment: - Possibility of operating in Air-Gapped mode - Microservice architecture - Stateless, perfectly scalable operation - Compatible with all Cloud Provider solutions: - [AWS via Marketplace](/documentation/developer/self-hosted-deployment/deploy-on-aws.md) - [Amazon Elastic Container Service (ECS)](/documentation/developer/self-hosted-deployment/deploy-on-aws-ecs.md) - [Google Cloud Run](/documentation/developer/self-hosted-deployment/deploy-on-gcp.md) - [Azure Container Apps](/documentation/developer/self-hosted-deployment/deploy-on-azure.md) - [Kubernetes](/documentation/developer/self-hosted-deployment/deploy-on-kubernetes.md) - [Clever Cloud](https://www.clever.cloud) - [Scaleway](https://www.scaleway.com) - [OVHCLoud](https://www.ovhcloud.com) Please read [On-Premise documentation](/documentation/developer/on-premise-installation/introduction.md). For more information, we invite you to [talk to us](https://carboneio.pipedrive.com/scheduler/5Rzzbxu6/carbone-on-premiseaws-presentation). --- ## Template Design #### [Getting started](https://carbone.io/documentation/design/overview/getting-started.md) Design your first template ##### Essential knowledge - **What is a template?** A document created with the tool of your choice. It can be a DOCX, PPTX, ODT, HTML, MD, etc. Carbone supports almost [any type of editable document](/documentation/design/overview/template-feature.md). - **What is a JSON?** A way to organize and share information in a format that is easy for both people and computers to understand. The JSON is the data coming directly from your existing applications. [Learn JSON](https://www.youtube.com/results?search_query=learn+json+for+beginners). - **What is a Carbone Tag?** A placeholder (also called "merge field") in your template that indicates where data should be inserted. Carbone tags are written between curly braces `{}`. Types of Carbone tags: - `{d.}`: Injects data from the main `data` object. [Examples](/documentation/design/substitutions/the-basics.md) - `{c.}`: Injects data from the `complement` object. [More info](/documentation/developer/http-api/generate-reports.md#generate-a-report-request-body) - `{# }`: Special tag to declare an [alias](/documentation/design/substitutions/aliases.md) (shortcut). - `{t( )}`: Special tag for [multi-language translation](/documentation/design/advanced-features/translations-i18n.md) - `{o.}`: Special tag to apply Carbone options: [pre-release tags](/documentation/design/overview/version-lifecycle.md#pre-release-features) and [arithmetic precision](/documentation/design/computation/simple-mathematics.md#arithmetic-precision). - **How does Carbone work?** Carbone merges your template with the data and generates a new document. ![Carbone Architecture](/img/doc/carbone-workflow.png) - **What is a Carbone Formatter?** A function (e.g., `:lowerCase`) that is added to the end of a Carbone tag to modify the data. What you can do with [formatters](/documentation/design/formatters/overview.md): - Format [numbers](/documentation/design/formatters/number.md), [dates](/documentation/design/formatters/date.md), [strings](/documentation/design/formatters/text.md), [currencies](/documentation/design/formatters/currency.md), etc. - [Create conditions](/documentation/design/conditions/overview.md), - Perform [mathematical computations](/documentation/design/computation/simple-mathematics.md), [aggregations](/documentation/design/computation/aggregation.md), etc - Use them for advanced features such as [inserting images](/documentation/design/advanced-features/pictures.md), [merging PDFs](/documentation/design/advanced-features/file-operations.md), [parsing HTML](/documentation/design/advanced-features/html.md), or [applying colors](/documentation/design/advanced-features/colors.md), etc. ##### Generate a document Designing a template is 80% about mastering your text editor (e.g., Microsoft Word, LibreOffice, OnlyOffice) and 20% about understanding the Carbone language, which is fully covered in this documentation. - Use your text editor to add elements such as headers, footers, [page numbers](/documentation/design/advanced-features/pagination.md#page-numbers), and [table of contents](/documentation/design/advanced-features/pagination.md#table-of-content), ... - Add Carbone tags to inject your data and see the result Ready to **generate your first** document with Carbone? Just [follow this quickstart tutorial](/documentation/quickstart/getting-started/generate-a-document.md) to get started, then get inspired by one of our [examples](/examples/index.md). ##### Next steps **We highly recommend starting with these two sections:** - Learn the basics: [Substitutions](/documentation/design/substitutions/the-basics.md) - Learn how to create loops to print a list of elements: [Repetitions](/documentation/design/repetitions/with-arrays.md) The documentation is written with many easy-to-read examples, generated and executed by the latest Carbone version itself! This ensures the content is always up-to-date. **You may also be interested in:** - [Configuring Microsoft Word/LibreOffice to simplify writing single quotes](/documentation/design/overview/design-best-practices.md#configuration-of-microsoft-word-and-libreoffice) - [Choosing the best template type for your needs](/documentation/design/overview/design-best-practices.md#frequently-asked-questions-which-template-type-should-i-use) - [Making Carbone tags more compact](/documentation/design/overview/design-best-practices.md#frequently-asked-questions-how-can-i-make-the-carbone-tag-more-compact) - [Understanding the main supported templates and their features](/documentation/design/overview/template-feature.md) - [Using spreadsheet (XLSX, ODS) formulas in a template](/documentation/design/overview/design-best-practices.md#frequently-asked-questions-can-i-use-spreadsheet-xlsx-ods-formulas-in-a-template) - [Exploring template examples](/examples/index.md) - The [help center](https://help.carbone.io), which includes some (old) [tutorials](https://help.carbone.io/en-us/category/tutorials-1qokelh/) 👋 If you’re stuck, feel free to contact our support team via chat! --- #### [Template feature](https://carbone.io/documentation/design/overview/template-feature.md) Supported template files and features ##### Template types Carbone supports virtually **any document as a template.** Carbone can also be used to generate emails in HTML, JSON, files for industrial label printers, markdown, and more. However, some advanced features require specific template files. This table provides a non-exhaustive overview of all supported features by template type. Carbone also manages more than 160 document conversions (ex., DOCX to PDF). The bottom of the table shows the most commonly used output file type. Here is [the complete list of supported output file formats](/documentation/developer/http-api/generate-reports.md#output-file-type) | Input Template Formats | XML | HTML | ODT | ODS | ODP | DOCX | XLSX | PPTX | ODG | SVG | PDF | MD | IDML | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | **Community Features** | | | | | | | | | | | | | | | Substitutions | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repetitions | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠️ | ✓ | ⚠️ | | Formatters | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Translations | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Conditions | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠️ | | Simple math | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | **Enterprise Features** | | | | | | | | | | | | | | | Aggregators | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pictures | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | | Colors | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 📅 | 📅 | 📅 | ✓ | ✗ | ✓ | ✗ | | HTML | ✗ | ✓ | ✓ | 📅 | 📅 | ✓ | 📅 | 📅 | ✗ | ✗ | ✗ | ✓ | ✗ | | Charts | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | | Barcodes | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | | Hyperlinks | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | | Forms | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | | Transform | ✗ | ✗ | ✓ | 📅 | ✓ | 📅 | 📅 | ✓ | 📅 | ✗ | ✗ | ✗ | ✗ | | File operations | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | | Signatures | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | | **Output File Formats** ([see full list](/documentation/developer/http-api/generate-reports.md#output-file-type)) | xml | html pdf txt | odt docx pdf txt jpg png epub md rtf html | ods xlsx csv pdf txt html | odp pptx pdf jpg png gif svg webp | odt docx pdf txt jpg png epub md rtf html | ods xlsx csv pdf txt html | odp pptx pdf jpg png gif svg webp | jpg png pdf webp epub cdr | pdf jpg png webp | pdf odt | pdf docx odt jpg png | idml | **Legend** - ✓ Working - ⚠️ Working with some limitation - 📅 On the roadmap - ✗ Not available or not supported by the file format itself It is worth mentioning that Carbone also supports all these features through Microsoft Word, OnlyOffice, LibreOffice or Google Docs: - Watermark - Header / Footer - Pagination, page count, page total - Pivot table - Bookmark, Cross-reference - Page size, page orientation - Repeat table header on new page - Page break - Automatic table resizing - Automatic text wrap - All font style / color / paragraph size - Comments - Footnotes - Table of Contents This list is not exhaustive. If you have questions, reach us on the chat. ##### XLSX Templates Carbone v5 has significantly improved support for XLSX templates. **Known Limitations:** - **Sheet names** have a [maximum limit of 31 characters](https://support.microsoft.com/en-us/office/rename-a-worksheet-3f1f7148-ee83-404d-8ef0-9ff99fbad1f9#:~:text=Important%3A%20Worksheet%20names%20cannot%3A,Contain%20more%20than%2031%20characters.) If Carbone injects data into a sheet name, ensure that the injected string does not exceed 31 characters. If exceeded, Excel will display a warning when opening the generated file: _"We found a problem with some content in 'file.xlsx'. Do you want us to try to recover as much as we can? If you trust the source of this workbook, click Yes."_ - **Dynamic sheet creation is not yet supported** in XLSX templates. As an alternative, use ODS templates and convert them to XLSX with Carbone. - **For Carbone v4 users:** Add the [prerelease tag](/documentation/design/overview/version-lifecycle.md#pre-release-features) to your template to access the latest features and bug fixes for XLSX templates. ##### PPTX Templates **Known Limitations:** - **Dynamic slide creation is not yet supported** in PPTX. As an alternative, use ODP templates and convert them to PPTX with Carbone. - In presentations, images and shapes are positioned absolutely. So, if you create a loop, new elements will overlap. Use the [formatter :transform](/documentation/design/advanced-features/transform.md) to move items along the X or Y axis in a loop. See a [real example here](/examples/matrix-pptx/index.md). --- #### [Version lifecycle](https://carbone.io/documentation/design/overview/version-lifecycle.md) Learn how to upgrade and explore the features and versions of Carbone ##### Versions Here are the available Carbone versions: - **Carbone v5**: Ready for production - **Carbone v4**: Ready for production, especially with the [latest prerelease features](/documentation/design/overview/version-lifecycle.md#pre-release-features) activated. - **Carbone v3**: Maintained but not recommended for new projects. - **Carbone v2**: Not supported. Please avoid using this version. We work hard to ensure [backward compatibility](/documentation/design/overview/philosophy.md) through 5000+ automated tests of the Carbone templating language. However, even though we are AI-powered humans, bugs can still happen. Therefore, we strongly recommend thoroughly testing your reports when migrating from one version to another. In most cases, generated PDFs may look slightly different due to improvements in our external document converters (LibreOffice, OnlyOffice), which support more DOCX and ODT features. If you encounter any regressions, please contact us. We’re here to help 👋. ##### Pre-release features Some upcoming features [are delivered in advance](/documentation/design/overview/version-lifecycle.md#feature-delivery-lifecycle) in the stable version. Here are the current pre-release tags for **Carbone v5**: - `{o.preReleaseFeatureIn=5002000}`: Enable Table Element, Heading Elements, and Page Break support with the `:html` formatter. Here are the current pre-release tags for **Carbone v4**: - `{o.preReleaseFeatureIn=4025011}` = "Enable all pre-release features added up to v4.25.11. It includes all other pre-release tags" - `{o.preReleaseFeatureIn=4024000}` - `{o.preReleaseFeatureIn=4022011}` - `{o.preReleaseFeatureIn=4022008}` - `{o.preReleaseFeatureIn=4015000}` - `{o.preReleaseFeatureIn=4009000}` To activate all upcoming features, add the tag `{o.preReleaseFeatureIn=4024000}` in the template's comment properties. These features will be activated by default in the next major release (v5, v6, etc.). Please take a look at the [changelog](/changelog.md) to see related features. - In Microsoft Office for Windows: [Follow this video](https://support.microsoft.com/en-us/office/video-change-document-properties-d23cc86c-bbd9-4e0b-b821-16ff90970d3e) - In LibreOffice on Mac or Windows : File -> Properties - In Microsoft Office for Mac / Linux : File -> Properties ![Insert pre-release tag in comment properties of the template](/img/doc/prerelease-tag.webp) You can also activate it via API or in the configuration. ##### Feature delivery lifecycle We typically follow these steps for each new feature or fix: 1. A customer requests a new feature or reports a needed fix through our chat. 2. The feature or fix is delivered in the `staging.x.x` Cloud environment, initially for testing with selected clients. 3. It then becomes available in version `V.x.x.x` for everyone (cloud, on-premises, public release notes, etc.). 4. Sometimes, new features are deactivated and must be activated with [optional flags](#pre-release-features) in the template or via API. These features will be activated by default in the next major release (v5, v6, etc.). This approach minimizes the risk of impacting other features and reduces time-to-market. How much time is needed to add a feature (for `Advanced` subscription)? - Urgent bugs are usually fixed within 24 hours. - Simple features are delivered in about 10 days. - Complex features require more time and may need further analysis. _Our record is 1 hour from the moment the client reports the issue to its deployment in production 💪._ --- #### [Design best practices](https://carbone.io/documentation/design/overview/design-best-practices.md) Configuration of your tools and frequently asked questions ##### Configuration of Microsoft Word and LibreOffice Carbone uses single quotes `'` (also known as **straight quotes**) to delimit text in formatter arguments, such as: `{d.value:ifEQ(true):show('This is true!')}`. Other types of quotes, such as double quotes, smart quotes, or chevrons, are not recognized by Carbone. Text editors may automatically replace "straight quotes" with "smart quotes" which are not supported by Carbone. **Solution:** - **MS Word**: Go to the `Tools` > `AutoCorrect Options` > `AutoFormat As You Type` tab and ensure that the "Straight quotes" with “smart quotes” checkbox is cleared. - **LibreOffice**: Go to `Tools` > `AutoCorrect` > `Localized Options`. Ensure that the "Replace" checkboxes for single and double quotes are disabled. ##### Designer guide - String arguments of formatters must be surrounded by single-quote `'`. Do not use double-quotes or smart-quotes. See above. - Avoid using floating elements inside a Carbone loop. Check if the anchor of special elements (images, shapes, text boxes) are set "In line with text". - Use the [drop formatter](/documentation/design/conditions/smart-conditions.md#drop-keep-element) to delete elements: images, paragraphs, table rows, shapes and charts instead of [showBegin/showEnd/hideBegin/hideEnd](/documentation/design/conditions/conditional-blocks.md) - Store variable in [temporary variable](/documentation/design/computation/store-and-transform.md) or use [aliases](/documentation/design/substitutions/aliases.md) to reduce the size of Carbone tags - Use [free and open source fonts](/documentation/design/overview/font-support.md) for commercial use to avoid license issues ##### Frequently Asked Questions ###### Which template type should I use? If your final document is a PDF, it's best to use DOCX or ODT templates. These formats offer robust support for pagination, headers, footers, and handle page text overflow more effectively because text and images are positioned relative to each other. In contrast, texts and images in presentation formats (PPTX, ODP) are positioned absolutely, and each slide is independent. This means that if a loop generates too much content for one slide, a new slide will not be created automatically. You will need to manage this yourself using loop filters. For example, you can display the first 10 items on slide N, the next 10 items on slide N+1, and so on. Some people use spreadsheets to create PDF documents. While this works, there are limitations that aren't necessarily related to Carbone. Others use HTML templates to generate PDFs, but it cannot compete with certain features provided by DOCX or ODT, such as repeating the header of a table on new pages. For other document types, you can use any template you prefer. [Here is a list](/documentation/design/overview/template-feature.md) of the most commonly used template file types with available Carbone feature. You can even generate JScript file for industrial label printers made by Zebra and CAB! ###### How can I make the Carbone tag more compact? There are two solutions: - Use [aliases](/documentation/design/substitutions/aliases.md) to reduce code repetition. - Only the font style and size of the first character `{` is taken into account in the generated document. You can reduce the font size of 99% of the Carbone tag like this: `{d.my.very.long.carbone.tag}` ###### Is it possible to bold text in the middle of a Carbone tag? No, the tag is treated as a single piece. The printed style is based on the style of the first `{` in the tag. ###### How do I remove the last page break or comma in a loop? Keep in mind that Carbone always repeats the i-th part, which includes the comma or the page break. Here are two solutions to overcome this: - **For page breaks:** Refer to this [tutorial](https://help.carbone.io/en-us/article/how-to-insert-a-page-break-without-having-a-blank-page-j6jy00/). In the tutorial, `{d.list[i]..list:len():sub(1):ifEQ(.i):drop(p)}` can also be replaced with the shorter version `{d.list[i, i=-1]:ifNEM:drop(p)}`. - **For a list of items:** Use [array formatters](/documentation/design/formatters/array.md#arrayjoin-separator-index-count) to flatten an array without using a loop `[i] -> [i+1]`. ###### Can I use spreadsheet (XLSX, ODS) formulas in a Template? Spreadsheet formulas (XLSX, ODS) can be used in Carbone templates. However, it's important to keep in mind that everything (cells, rows, sheets) can move when Carbone injects data into the template: - Carbone does not interpret or adjust spreadsheet formulas (not yet!). It simply injects data into the spreadsheet without recognizing the presence of formulas. - Carbone inserts the data **first** without computing any spreadsheet formulas. **Then**, it executes all spreadsheet formulas using its internal LibreOffice/OnlyOffice engine. - And Excel and LibreOffice are not aware of Carbone's data injection. Therefore, all preexisting spreadsheet formulas in the template must take into account for these potential shifts. This tutorial explains some of the consequences of this process, particularly with totals: [How to Compute Totals in Excel Spreadsheets When Carbone Injects New Lines](https://help.carbone.io/en-us/article/how-to-compute-totals-in-excel-spreadsheets-when-carbone-injects-new-lines-1iat7yy/). ###### How can I do `{d.val} * 100` in a spreadsheet (XLSX, ODS)? **Solution 1:** Use only Carbone features: `{d.val:mul(100)}`. **Solution 2:** If you want to perform calculations in Excel, you must isolate the Carbone tag in a separate cell and use `:formatN` to ensure that the number is converted to an Excel-compatible number format. - Cell A1: `{d.val:formatN}` - Cell A2: `= A1 * 100` **Solution 3:** Include the Carbone tag in the Excel formula and use `IFERROR` to let Excel accept the Carbone tag: `= 100 * IFERROR("{d.val}", 0)` ###### How can I make the document generation faster? Carbone is highly optimized. - 99% of the time is spent in LibreOffice for document conversion (DOCX -> PDF, ODT -> PDF...). - 1% in Carbone for injecting the data, when there is no conversion Here are few tips to optimize the document PDF conversion: - Don't use "Text Wrapping -> Arround" option for tables, charts, or images, as LibreOffice converter has many bugs related to this feature : ![Converter performance for big file](/img/v5/bug-libreoffice.png) - Remove embedded fonts in your template - Use ODT templates instead of DOCX templates - Reduce image resolution - Export the document in the same format as the template (DOCX -> DOCX), and perform the PDF conversion on your own. - Select a different document converter. Carbone v5 offers new options: `OnlyOffice`, `Chromium`. - Use asynchronous API ###### Some computed fields are not updated in DOCX/ODT (total page number, ...) You can fix this issue by setting `hardRefresh: true` when calling the API. This forces Carbone to use LibreOffice to update all computed fields in the DOCX. Another solution is to add a hidden page number (in white text) to your document. Carbone will automatically set `hardRefresh: true`. To do this: **Insert → Page Number**. --- #### [Fonts support](https://carbone.io/documentation/design/overview/font-support.md) List of supported font by Carbone ##### Supported fonts Carbone supports all fonts. However, if your template is converted to another file format (ex. DOCX to PDF), the fonts must be installed on the machine where Carbone is executed. We recommend using one of our 6000 officially supported fonts. Most of these fonts can be found on [Google Font](https://fonts.google.com/?sort=popularity). | | Carbone Cloud | Carbone On-Premise | Embedded CarboneJS | | --- | --- | --- | --- | | [\> 6000 fonts free for commercial use](/img/doc/carbone-cloud-font-list.txt) | ✓ | 🛠 | 🛠 | | Auto-replacement of unknown fonts | ✓ | 🛠 | 🛠 | | Unknown font free for commercial use | ℹ️ | 🛠 | 🛠 | | Fonts with paid licenses | ✗ | 🛠 | 🛠 | | Private fonts | ✗ | 🛠 | 🛠 | _Legend_ - ✓ Supported - ℹ️ Contact support to add the font - 🛠 Supported ; Needs expertise on your side **What happens if a recipient of a DOCX or PDF document does not have the font installed?** - For PDF: The rendering will be perfect, even if the viewer does not have the font installed. - For DOCX/ODT/PPTX/.../XLSX: The rendering will be correct only if the viewer has the font installed on their system. To avoid this issue, you can embed fonts in the template. However, use this option with caution (see details below). We recommend using well-known fonts such as Calibri, Arial, Times, Aptos, and others. ##### Embedded Fonts Microsoft Word and LibreOffice offer an option to embed the fonts used in templates (PPTX, DOCX, ODT, etc.). This option should be used with caution as it significantly increases the template size, making processing slower. However, it can be a solution for using private fonts that are not available on Carbone's servers (Cloud or On-Premise) or on the recipient's system. It works only if all the following conditions are met: - Use TTF fonts whenever possible. - Avoid variable fonts; only static fonts are supported. - The font license must allow embedding. Each font has a `fsType` field, and it must be set to `0` ("Installable Embedding"). - Font names stored in DOCX files must match the actual font names (Microsoft Word does not always handle this correctly). Carbone v5 will attempt to fix this issue automatically. Here are some online tools to check font information: - [Wakamai Fondue](https://wakamaifondue.com/) - [FontDrop!](https://fontdrop.info/) ##### Unicode / CJK Fonts For international documents, we recommend using **Noto Fonts**. Noto Fonts is one of the most advanced font families, supporting **all languages** (over 1000) worldwide. Avoid using the well-known Arial Unicode MS font for handling Hindi, Chinese, or Japanese characters. Arial Unicode MS is not free, and Microsoft stopped distributing it in 2016, meaning it is now deprecated. [Learn more here](https://devblogs.microsoft.com/oldnewthing/20181030-00/?p=100085) [or here](https://en.wikipedia.org/wiki/Arial_Unicode_MS). Here is a list of free and actively maintained Unicode / CJK (Chinese/Japanese/Korean) fonts: - [Unicode fonts](https://en.wikipedia.org/wiki/Unicode_font) - [Chinese/Japanese/Korean fonts](https://en.wikipedia.org/wiki/List_of_CJK_fonts) If you want to use one of these fonts with Carbone Cloud, please let us know. We can install only free fonts (indicated by the green `[F]` in the Wikipedia table). --- #### [Philosophy](https://carbone.io/documentation/design/overview/philosophy.md) Understand the Unique and Universal Carbone templating language ##### Our vision Each new version of Carbone follows these rules: - **Guaranteed backward compatibility**. A template created with Carbone v2 is still compatible with Carbone v12. - Design / JSON structure **isolation**. Preserve your existing JSON structure regardless of the template design. - **Faster/Simpler** than before. Each new version is faster and easier to use than the previous one. ##### What makes Carbone unique? Carbone is a **universal low-code templating language**. This templating language works virtually **with all documents (docx, odt, ods, md, xml, html, ...)** even if Carbone does not know the specification of the document. For computer scientists, it is an XML-agnostic algorithm. It uses a **simple declarative** programming language, which is **not intrusive**. You don't need to insert control flow codes such as `if` or `start/end-loop` tags in your document. This way of programming provides more flexibility for traveling data. You can transform your original JSON array directly in the template. See examples: - [Sorting](/documentation/design/repetitions/sorting.md) - [Bi-directional loop](/documentation/design/repetitions/with-arrays.md#bi-directional-loop) > In computer science, declarative programming is a programming paradigm, a style of building the structure and elements of computer programs, that expresses the logic of a computation without describing its control flow. > > Many languages applying this style attempt to minimize or eliminate side effects by describing what the program should accomplish in terms of the problem domain, rather than describing how to go about accomplishing it as a sequence of the programming language primitives (the how being left up to the language's implementation). > > This is in contrast with imperative programming, in which algorithms are implemented in terms of explicit steps. [Wikipedia source](https://en.wikipedia.org/wiki/Declarative_programming) ##### Why did we create Carbone? > ” In my first company (2011), we were only two developers and had many client demands. All clients wanted a personalized PDF showing the menu of their restaurant. > > Maintaining hard-coded reports with Wkhtml2pdf or Pdfkit was time-consuming and error-prone. Birt, Jasper Report and Talend tools were too complex to use and deploy. > > So, I came up with an idea: > > - We already have battle-tested JSON APIs for our web app > - Our clients are not computer scientists, but they know how to use Microsoft Office or LibreOffice > > Could we create a simple templating language which injects data inside a document **without modifying our JSON APIs**? > > After working hard for three twenty weekends, Carbone was born! > > Ten years later, my first company became the leading ERP software provider for hotels and restaurants in Europe, offering 100% highly personalized reports and serving millions of meals every day. > > And Carbone became an independent company with many new features and more supported template types, such as HTML. David. _Main author of Carbone._ ![Me in 2011](/img/doc/meIn2011.webp) --- #### [The basics](https://carbone.io/documentation/design/substitutions/the-basics.md) Learn how to use a simple Carbone placeholder tag COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v1.0+ Each part of the documentation shows exactly what happens if you send **Data** (JSON Object) and a **Template** (handmade docx, odt, ...) to Carbone. ##### Basic Carbone tags are placeholders `{d.}` within the template, then they are replaced with data from your JSON dataset. The value can be a string, a number, or a date. ```cdata { "firstname": "John", "lastname": "Doe" } ``` ```ctemplate Hello {d.firstname} {d.lastname} ! ``` ```cresult Hello John Doe ! ``` ##### Accessing sub-objects If a dataset contains sub-objects, Carbone can access them using the Dot Notation to go deeper in the object tree. ```cdata { "firstname": "John", "type": { "id": 1, "name": "human" } } ``` ```ctemplate {d.firstname} is a {d.type.name} ``` ```cresult John is a human ``` ##### Accessing arrays When data is an array of objects, you can access directly each object using the **reserved word `i`**, which represents the ith item of the array. Carbone uses zero-based arrays: - The first item of an array is `[i=0]` or `[0]`. - The second item of an array is `[i=1]` or `[1]` An array is reachable using the Square Bracket Notation `[]`. ```cdata [ { "movie": "Inception" }, { "movie": "Matrix" }, { "movie": "Back to the future" } ] ``` ```ctemplate Preferred movie are {d[i=1].movie} and {d[2].movie} ``` ```cresult Preferred movie are Matrix and Back to the future ``` Get inspired by one of our real-life examples: [Menu](/examples/menu/index.md) or [Planning](/examples/planification-simple/index.md) ##### Parent properties Access properties of the parent object with two points `..` (or more) ```cdata { "country": "France", "movie": { "name": "Inception", "sub": { "a" : "test" } } } ``` ```ctemplate {d.movie.sub..name} {d.movie.sub...country} ``` ```cresult Inception France ``` --- #### [Array search](https://carbone.io/documentation/design/substitutions/array-search.md) Search an item within an array without doing a loop COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### Simple filters Instead of using the **reserved word `i`** to [access the i-th item](/documentation/design/substitutions/the-basics.md#accessing-arrays) of an array, Carbone accepts filters using attributes of objects. If the filter returns many rows, Carbone keeps only the first occurrence. Use single quotes to filter text that contains spaces. ```cdata [ { "name": "Inception", "year": 2010 }, { "name": "Matrix", "year": 1999 }, { "name": "Back to the future", "year": 1985 } ] ``` ```ctemplate The movie of 1999 was {d[year=1999].name} Back to the future: {d[name='Back to the future'].year} ``` ```cresult The movie of 1999 was Matrix Back to the future: 1985 ``` ##### Variable filter UPDATED v4.2.0+ If the second operand starts with a period, Carbone considers that the operand is a variable coming from the JSON ```cdata { "parent" : { "qty" : 2 }, "subArray" : [{ "text" : 1000, "other" : 1, "sub" : { "b" : { "c" : 1 } } },{ "text" : 2000, "other" : 1, "sub" : { "b" : { "c" : 2 } } }] } ``` ```ctemplate {d.subArray[sub.b.c = .other].text} {d.subArray[sub.b.c = ..parent.qty].text} ``` ```cresult 1000 2000 ``` ##### Multiple array filters Multiple filters are accepted, even using a sub-object. If the filter returns many rows, Carbone keeps only the first occurrence. ```cdata [ { "name": "Interstellar" , "year": 2014, "meta": { "type": "SF" } }, { "name": "Matrix" , "year": 1999, "meta": { "type": "SF" } }, { "name": "The Green Mile", "year": 1999, "meta": { "type": "Drama" } } ] ``` ```ctemplate {d[year=1999, meta.type='SF'].name} ``` ```cresult Matrix ``` ##### Last element Get the last element of a list with a negative index `[i=-1]`. If you want to get the second to last, you have to use `[i=-2]`. Here is an example: This filter can be used also in a [repetition loop](/documentation/design/repetitions/filtering.md#exclude-the-last-n-items). ```cdata [ { "name": "Inception", "year": 2010 }, { "name": "Matrix", "year": 1999 }, { "name": "BTTF", "year": 1985 } ] ``` ```ctemplate The oldest movie was {d[i=-1].name}. ``` ```cresult The oldest movie was BTTF. ``` ##### Filter and print parent Access properties of the parent object with two points `..` (or more) when you need to print a parent property using filters in nested arrays: ```cdata { "country": "USA", "movies": [ { "name": "Inception", "year": 2010 }, { "name": "Matrix", "year": 1999 }, { "name": "BTTF", "year": 1985 } ] } ``` ```ctemplate {d.movies[year=1999]..country} ``` ```cresult USA ``` --- #### [Aliases](https://carbone.io/documentation/design/substitutions/aliases.md) Simplify Carbone tag with aliases COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### Alias Aliases can be used to simplify the maintenance of your report by avoiding repetition of code, or to insert tags where special characters such as square brackets are not allowed (for example, worksheet names). - Write `{#... = ...}` to define an alias - Write `{$...}` to use this alias Aliases can be defined anywhere in the document, at the end, or at the beginning. They are parsed and removed by Carbone before rendering. ```cdata { "name": "Cars", "wheels": 4 } ``` ```ctemplate {#myAlias = d.wheels} {d.name} need {$myAlias} wheels! ``` ```cresult Cars need 4 wheels! ``` Aliases do not store values. They are simply shortcuts. [More information](#more-information) Get inspired by one of our real-life examples: [Prescription](/examples/prescription/index.md), [Stock Inventory Spreadsheet](/examples/stock-inventory-spreadsheet/index.md) or [Planning with subtotals](/examples/planification-medium/index.md) ##### Parametrized Alias Aliases accept an unlimited number of parameters between parentheses, like a function in many languages. Each parameter must start by `$`. ```cdata [ { "name": "chicken", "weekday": 1 }, { "name": "fish" , "weekday": 2 } ] ``` ```ctemplate {#mealOf($weekday) = d[weekday = $weekday]} Tuesday, we eat {$mealOf(2).name}. ``` ```cresult Tuesday, we eat fish. ``` ##### More information An alias cannot store a value. It is just a shortcut. If you need to store a value, use the [:set formatter](/documentation/design/computation/store-and-transform.md): ```text {d.products[].price:aggSum:set(c.total)} {c.value} ``` Aliases are replaced by Carbone with the actual tag before processing the template. They act as a preprocessor tool (similar to `#define` in C language). For example: | Alias | Template | What Carbone Sees | Result | | --- | --- | --- | --- | | `d.obj` | `{d.val:mul($alias.qty)}` | `{d.val:mul(d.obj.qty)}` | OK | | `d.arr[i].obj` | `{d.val:mul($alias.qty)}` | `{d.val:mul(d.arr[i].obj.qty)}` | Not supported | The second case is not supported because Carbone does not support starting a loop with `d.arr[i].obj` inside a formatter. --- #### [Loop over array](https://carbone.io/documentation/design/repetitions/with-arrays.md) Repeat any part of you document COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### Overview Carbone can repeat any part of a document (rows, titles, pages...). Just write where the `i-th` and `i-th+1` rows are and Carbone will automatically find the pattern to repeat, using the first row `i-th` as an example. The second line (`i+1`) will be removed before rendering the result. Only one `[i+1]` tag is enough to find the repetition pattern. It is not necessary to repeat the whole `[i+1]` part. ##### Simple array In this example, we want to travel an array of cars. ```cdata { "cars" : [ {"brand" : "Toyota" , "id" : 1 }, {"brand" : "Hyundai", "id" : 2 }, {"brand" : "BMW" , "id" : 3 }, {"brand" : "Peugeot", "id" : 4 } ] } ``` **Template:** | Cars | id | | --- | --- | | {d.cars[i].brand} | {d.cars[i].id} | | {d.cars[i+1].brand} | | **Result:** | Cars | id | | --- | --- | | Toyota | 1 | | Hyundai | 2 | | BMW | 3 | | Peugeot | 4 | Get inspired by one of our real-life examples: [Menu](/examples/menu/index.md), [Simple Planning](/examples/planification-simple/index.md) or [Best Manufacturer Awards](/examples/classification-simple/index.md) ##### Nested arrays Carbone manages nested arrays (unlimited depth). Here is an example where a whole portion of a document is repeated. ```cdata [ { "brand": "Toyota", "models": [{ "size": "Prius 4", "power": 125 }, { "size": "Prius 5", "power": 139 }] }, { "brand": "Kia", "models": [{ "size": "EV4", "power": 450 }, { "size": "EV6", "power": 500 }] } ] ``` **Template:** | Models | | --- | | {d[i].models[i].size} - {d[i].models[i].power} | | {d[i].models[i+1].size} | **Result:** | Models | | --- | | Prius 4 - 125 | | Prius 5 - 139 | As you can see, it is useless to repeat the first paragraph twice in the template, only the title of the second paragraph is necessary to help Carbone recognize where the repetition pattern of the main array `{d.cars[i+1].brand}` ends. Get inspired by one of our real-life examples: [Best Selling Vehicles Awards](/examples/awards-simple/index.md) or [Stock Inventory Spreadsheet](/examples/stock-inventory-spreadsheet/index.md) ##### Bi-directional loop v4.8.0+ The bidirectional loop performs iterations in 2 directions, creating additional columns and rows. There are a few restrictions: - The JSON parent array must be used for rows, and the nested array must be used for columns. This limitation will be removed in Carbone v5. - Officialy supported in DOCX, HTML, MD templates. In addition, Word can automatically resize the table to fit the page width. Bidirectional loops with other template formats are still experimental. Please get in touch with us in the chat if necessary. ```cdata { "titles" : [{ "name": "Kia" }, { "name": "Toyota" }, { "name": "Hopium" }], "cars" : [ { "models" : [ "EV3", "Prius 1", "Prototype" ] }, { "models" : [ "EV4", "Prius 2", "" ] }, { "models" : [ "EV6", "Prius 3", "" ] } ] } ``` **Template:** | {d.titles[i].name} | {d.titles[i+1].name} | | --- | --- | | {d.cars[i].models[i]} | {d.cars[i].models[i+1]} | | {d.cars[i+1].models[i]} | | **Result:** | Kia | Toyota | Hopium | | --- | --- | --- | | EV3 | Prius 1 | Prototype | | EV4 | Prius 2 | | | EV6 | Prius 3 | | Get inspired by one of our real-life examples: [Store Inventory](/examples/shoes/index.md) or [Product Comparison Table](/examples/product-comparison-table/index.md) ##### Access the loop iterator value v4.0.0+ Access the iterator value when a list is printed in a document. For example: `{d[i].cars[i].other.wheels[i].tire.subObject:add(.i):add(..i):add(...i)}` The number of dots corresponds to the position of the `i` in the hierarchy: - `...i` refers to the index value of `wheels[i]` - `..i` refers to the index value of `cars[i]` - `.i` refers to the index value of `d[i]` The number of dots is currently inverted. It should be `...i` for `d[i]` and `.i` for `wheels[i]`. This is a known bug. However, many users rely on the current behavior, and fixing it would break some reports. We plan to address this issue in the future while ensuring backward compatibility. Get inspired by one of our real-life examples: [Summary](/examples/summary/index.md) ##### Parallel loop Iterate through two separate arrays at the same time using the same iterator variable. As seen [earlier](#access-the-loop-iterator-value), the iterator `i` can be used in formatters. By combining it with [relative path access](/documentation/design/formatters/overview.md#dynamic-parameters), we can navigate back in the JSON hierarchy using two dots (`..`). This allows us to print the content of another array, `brands`, using the same iterator as the `cars` array. ```cdata { "cars" : [ { "id" : 1 }, { "id" : 2 }, { "id" : 3 }, { "id" : 4 } ], "brands" : [ { "name" : "Toyota" }, { "name" : "Hyundai"}, { "name" : "BMW" } ] } ``` **Template:** | Cars | id | | --- | --- | | {d.cars[i].id} | {d.cars[i].id:print(..brands[.i].name)} | | {d.cars[i+1].id} | | **Result:** | Cars | id | | --- | --- | | 1 | Toyota | | 2 | Hyundai | | 3 | BMW | | 4 | | --- #### [Loop over object properties](https://carbone.io/documentation/design/repetitions/with-objects.md) Repeat any part of you document from object properties COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v3.0+ ##### Iterate over object properties - use `.att` to print the attribute - use `.val` to print the value ```cdata { "myObject" : { "paul" : "10", "jack" : "20", "bob" : "30" } } ``` **Template:** | People name | People age | | --- | --- | | {d.myObject[i].att} | {d.myObject[i].val} | | {d.myObject[i+1].att} | {d.myObject[i+1].val} | **Result:** | People name | People age | | --- | --- | | paul | 10 | | jack | 20 | | bob | 30 | --- #### [Sorting arrays](https://carbone.io/documentation/design/repetitions/sorting.md) Sort your data COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### Ascendant sort Carbone allows to use attributes of objects instead of the **reserved iterator `i`** to iterate over arrays. This can be used to sort data directly in the template. In this example, all cars are first sorted by `power` in ascending order, and if two items have the same power value, they are sorted by item position in the array `i`. ```cdata { "cars" : [ { "brand" : "Ferrari" , "power" : 3 }, { "brand" : "Peugeot" , "power" : 1 }, { "brand" : "BMW" , "power" : 2 }, { "brand" : "Lexus" , "power" : 1 } ] } ``` **Template:** | Cars | | --- | | {d.cars[power , i].brand} | | {d.cars[power+1, i+1].brand} | **Result:** | Cars | | --- | | Peugeot | | Lexus | | BMW | | Ferrari | ##### Multiple sort attributes Multiples attributes can be used to sort data. Here is an example to sort the array by `power`, `sub.size`, and array position `i`: ```cdata { "cars" : [ { "brand" : "Ferrari" , "power" : 3, "sub" : { "size" : 1 } }, { "brand" : "Aptera" , "power" : 1, "sub" : { "size" : 20 } }, { "brand" : "Peugeot" , "power" : 1, "sub" : { "size" : 20 } }, { "brand" : "BMW" , "power" : 2, "sub" : { "size" : 1 } }, { "brand" : "Kia" , "power" : 1, "sub" : { "size" : 10 } } ] } ``` **Template:** | Cars | | --- | | {d.cars[power , sub.size , i].brand} | | {d.cars[power+1, sub.size+1, i+1].brand} | **Result:** | Cars | | --- | | Kia | | Aptera | | Peugeot | | BMW | | Ferrari | ##### Descendant Sort Coming soon in Carbone v5 --- #### [Filtering arrays](https://carbone.io/documentation/design/repetitions/filtering.md) Filter your array COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ Tips: To hide a row in a table with a complex condition, using a [Smart Conditional Block](/documentation/design/conditions/smart-conditions.md#drop-keep-element) is often easier to manage. See the simple [example below](#smart-filter). ##### New syntax in v5+ NEW v5.0.0+ Prior to Carbone v5, filters needed to be repeated on all `[i]` tags, like in most example on this page. With **v5 and later, a single `[i+1]` tag containing the filter is sufficient**. Repeating the filter is unnecessary. It works for all types of array filters described on this page. Carbone v5 still supports the v4 syntax for backward compatibility, where the filter must be repeated on all tags. ```cdata [ { "name": "John", "age": 20 }, { "name": "Eva", "age": 18 }, { "name": "Bob", "age": 25 }, { "name": "Charly", "age": 30 } ] ``` **Template:** | People | | --- | | {d[i].name} {d[i].age} | | {d[i+1, age > 19, age < 30].name} | **Result:** | People | | --- | | John 20 | | Bob 25 | ##### Number filters You can use conditional operators to filter arrays: `>` , `<` , `>=` , `<=` , `=`, `!=`. Since Carbone v5, filters must be the same on all tags if you want to keep only mat ching rows. Otherwise, only the tag is hidden, not the entire row. Use [Alias](/documentation/design/substitutions/aliases.md) to simplify report maintenance and avoid repetitions of code. ```cdata [ { "name": "John", "age": 20 }, { "name": "Eva", "age": 18 }, { "name": "Bob", "age": 25 }, { "name": "Charly", "age": 30 } ] ``` **Template:** | People | | --- | | {d[i, age > 19, age < 30].name} | | {d[i+1, age > 19, age < 30 ].name} | **Result:** | People | | --- | | John | | Bob | You can add multiple filters by separating them with commas. Each comma represents an `AND` operator, so all conditions must be met for the filter to apply. If you need to use an `OR` operator, see the [advanced filter example](#advanced-filter). ##### String filters String values can also be used for filtering, as shown in the following example. Both `=` and `!=` operators are supported. ```cdata [ { "name": "Falcon 9", "type": "rocket" }, { "name": "Model S", "type": "car" }, { "name": "Model 3", "type": "car" }, { "name": "Falcon Heavy", "type": "rocket" } ] ``` **Template:** | People | | --- | | {d[i , type='rocket'].name} | | {d[i+1, type='rocket'].name} | **Result:** | People | | --- | | Falcon 9 | | Falcon Heavy | ##### Filter the first N items Filtering the loop index is also possible as the following example: ```cdata [ { "name": "Falcon 9" }, { "name": "Model S" }, { "name": "Model 3" }, { "name": "Falcon Heavy" } ] ``` **Template:** | People | | --- | | {d[i, i < 2].name} | | {d[i+1, i < 2].name} | **Result:** | People | | --- | | Falcon 9 | | Model S | Get inspired by one of our real-life examples: [Best Manufacturer Awards](/examples/classification-simple/index.md) ##### Exclude the last N items Negative loop index `i` is accepted to print all elements except the last N-th items: ```cdata [ { "name": "Falcon 9" }, { "name": "Model S" }, { "name": "Model 3" }, { "name": "Falcon Heavy" } ] ``` **Template:** | People | | --- | | {d[i, i!=-1].name} | | {d[i+1, i!=-1].name} | **Result:** | People | | --- | | Falcon 9 | | Model S | | Model 3 | ##### Advanced filter NEW v5.0.0+ When your filter is complex (for example, if you need "OR" logic, or mathematical calculations), you can first store an intermediate result using the `set` formatter. In the example below, we want to keep only the rows where the name contains either "3" or "9". The [set formatter](/documentation/design/computation/store-and-transform.md#modify-json-set-relativepath) is used to create a new attribute called `.isShown` in the JSON, which indicates whether each item matches the complex condition: `{d[].name:ifIN(3):or:ifIN(9):show(1):elseShow(0):set(.isShown)}` Then, you can use this new attribute as a filter for the array. ```cdata [ { "name": "Falcon 9" }, { "name": "Model S" }, { "name": "Model 3" }, { "name": "Falcon Heavy" } ] ``` **Template:** | People | | --- | | {d[i].name} | | {d[i+1, isShown=1].name} | **Result:** | People | | --- | | Falcon 9 | | Model 3 | ##### Smart filter Sometimes, it is easier to use [Smart Conditional Block](/documentation/design/conditions/smart-conditions.md#drop-keep-element) to hide a row in a table: You can use complex conditions with formatters. ```cdata [ { "name": "Falcon 9" }, { "name": "Model S" }, { "name": "Model 3" }, { "name": "Falcon Heavy" } ] ``` **Template:** | People | | --- | | {d[i].name}{d[i].name:ifIN('Falcon'):drop(row)} | | {d[i+1].name} | **Result:** | People | | --- | | Model S | | Model 3 | --- #### [Grouping](https://carbone.io/documentation/design/repetitions/grouping.md) Group by any item of your data or change the orginal structure of the JSON data source COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v5.0+ NEW ##### Group and sum A custom iterator can be used to group rows based on the value of a specific attribute (in this case, `brand`). Additionally, with [aggregator formatters](/documentation/design/computation/aggregation.md), you can sum quantities: ```cdata [ { "brand":"Lucid" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Lucid" , "qty":10 } ] ``` **Template:** | Vehicles | Quantity | | --- | --- | | {d[brand].brand} | {d[brand].qty:aggSum(.brand)} | | {d[brand+1].brand} | | **Result:** | Vehicles | Quantity | | --- | --- | | Aptera | 1 | | Faraday | 6 | | Lucid | 11 | | Venturi | 3 | Get inspired by one of our real-life examples: [Bank Statement](/examples/bank-statement-expert/index.md), [Planning with subtotals](/examples/planification-medium/index.md), [Invoice](/examples/invoice-simple/index.md), or [Store Inventory](/examples/shoes/index.md) ##### Transform the result of a database NEW v5.0.0+ Or since 4.23.0+ with [activated pre-release tag](/documentation/design/overview/version-lifecycle.md#pre-release-features) Carbone can also transform the JSON with the `:set` formatter. [More information here](/documentation/design/computation/store-and-transform.md). For example, if the JSON is a flat array coming directly from a database (MySQL, MariaDB, PostgreSQL, Oracle, MS SQL), Carbone can build a hierarchical JSON with just a few lines of code! ```cdata [ { "country" : "France", "city" : "Paris" }, { "country" : "France", "city" : "Nantes" }, { "country" : "France", "city" : "Pouzauges" }, { "country" : "Italy" , "city" : "Rome" }, { "country" : "Italy" , "city" : "Venise" } ] ``` ```ctemplate {d[].city:set(c.countries[id=.country].cities[].name)} ``` ```cresult ``` Get inspired by one of our real-life examples: [Gallery](/examples/gallery-simple/index.md) --- #### [Distinct](https://carbone.io/documentation/design/repetitions/distinct.md) Print only distinct value COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### Distinct items A custom iterator can be used to select distinct rows according to the attribute's value. ```cdata [ { "type": "car" , "brand": "Hyundai" }, { "type": "plane", "brand": "Airbus" }, { "type": "plane", "brand": "Boeing" }, { "type": "car" , "brand": "Toyota" } ] ``` **Template:** | Vehicles | | --- | | {d[type].brand} | | {d[type+1].brand} | **Result:** | Vehicles | | --- | | Hyundai | | Airbus | --- #### [Lookup](https://carbone.io/documentation/design/repetitions/lookup.md) Find a value in a list or dictionary using a search filter COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v5.2+ NEW ##### Lookup values from another array NEW v5.2.0+ To use this early-adopter feature, you must enable the pre-release flag by setting `{o.preReleaseFeatureIn=5002000}`. [Learn how to activate pre-release features here.](/documentation/design/overview/version-lifecycle.md#pre-release-features) It’s common to have an array where you only store the ID of an object, and want to display a property from the related object stored in a different array. This is called a "lookup" or a "join" between arrays (like a join in a database, or VLOOKUP in Excel). For example, imagine you have one array with movie actors, and another with movies, where each movie only stores the actor's ID. You want to print the name of each movie along with the main actor’s first name. ```cdata { "movies": [ { "actorId" : 10, "name" : "Matrix" }, { "actorId" : 20, "name" : "Babylon" }, { "actorId" : 10, "name" : "John Wick" } ], "actors": [ { "id" : 10, "firstName" : "Keanu" }, { "id" : 20, "firstName" : "Margot" } ] } ``` **Template:** | Movie Name | Main Actor {o.preReleaseFeatureIn=5002000} | | --- | --- | | {d.movies[i].name} | {d.movies[i].actorId:print(..actors[id=.actorId].firstName)} | | {d.movies[i+1].name} | | **Result:** | Movie Name | Main Actor | | --- | --- | | Matrix | Keanu | | Babylon | Margot | | John Wick | Keanu | ##### Detailed Documentation: Lookup Syntax You can use several types of **search expressions** to look up values from another array. Here’s how each type works: ###### Index Lookup: `[INTEGER]` Selects the item at a specific index in the target array. - **Example:** `{d[i].movies[i].id:print(..actors[10].name)}` This prints the name of the actor at index `10` of the `actors` array. ###### Current Iterator Lookup: `[.i]` This method selects the item from the target array that shares the same index as the current item in your loop. For a deeper explanation, see the [Access the Loop Iterator Value](/documentation/design/repetitions/with-arrays.md#access-the-loop-iterator-value) section. - **Example:** `{d.movies[i].id:print(..otherTable[.i].name)}` This prints the name at the same index in `otherTable` as the current index in `movies`. ###### Equality Lookup: `[X = Y]` Finds the first element in the target array **where a field matches a value**, like a SQL join or Excel VLOOKUP. - **Usage Example:** `{d.movies[i].actorId:print(..actors[id=.actorId].firstName)}` - This finds the item in `actors` where `id` equals the current movie’s `actorId` and returns `firstName`. - **One equality per lookup:** You can use only one `=` comparison in each lookup. - **Left side (`LEFT_OPERAND`):** - Refers to a property of each item in the searched array (like `id` or `.id`). - If you write something like `d.sub.id`, here, `d` is interpreted as a property of the searched item, **not** an absolute path. - **Right side (`RIGHT_OPERAND`):** - Can be **a relative path** from the main tag (starting with `.`), or **an absolute path** starting with `d.` or `c.` - Most common: use a relative path (like `.actorId`) to reference a field in your data source. - If `RIGHT_OPERAND` doesn't start with `.`, `d.`, or `c.`, it is treated as a fixed string. - _Note:_ these constant strings can’t have spaces. - Make sure the data types on both sides of the `=` are the same (for example, both should be numbers, or both should be strings). ###### Summary Table | Syntax Type | Description | Example | | --- | --- | --- | | `[NUMBER]` | Select by index | `{...:print(..array[2].field)}` | | `[.i]` | Use current index from main loop | `{...:print(..array[.i].field)}` | | `[field = .otherField]` | Join with array item where field matches value | `{...:print(..array[id=.someId].name)}` | | `[field = text]` | Lookup constant value (no spaces in the constant) | `{...:print(..array[name=Tom].score)}` | --- #### [Overview](https://carbone.io/documentation/design/formatters/overview.md) Translate raw data into human readable text. COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ Formatters can be used to translate raw data into human readable text. A formatter is applied to data using the separator `:`. Multiple formatters can be used one after another, each formatter input is the output of the previous one. Some formatters accept [constant](#constant-parameters) or [dynamic parameters](#dynamic-parameters). Here is an example showing how to write `"John"` instead of `"JOHN"` using two chained formatters, and how to translate a raw ISO date into a human readable date. ```cdata { "name" : "JOHN", "birthday" : "2000-01-31" } ``` ```ctemplate My name is {d.name:lowerCase:ucFirst}. I was born on {d.birthday:formatD(LL)}. ``` ```cresult My name is John. I was born on January 31, 2000. ``` ##### Constant parameters Many formatters accept one or multiple parameters, separated by commas, enclosed in parentheses to modify the output. For example, the formatter `:prepend(myPrefix)` adds the prefix "myPrefix." If your constant parameter contains a **comma** or **whitespace**, you must wrap the text with single quotes (straight quotes): `prepend('my prefix')`. [Configure your text editor](/documentation/design/overview/design-best-practices.md#configuration-of-microsoft-word-and-libreoffice) to prevent quote replacement. ##### Dynamic parameters Formatters accept dynamic variables if parameters start with a `.` and is not surounded by quotes. Carbone accepts two methods: - with an **absolute JSON path**, starting with `d.` (root of the data object) or `c.` (root of the complement object) with some limitations - with a **relative JSON path** from the parent object, starting with a dot `.` Here is the dataset used for the following examples: ```json { "id" : 10, "qtyA" : 20, "subObject" : { "qtyB" : 5, "qtyC" : 3 }, "subArray" : [{ "id" : 1000, "qtyE" : 3 }] } ``` Do a mathematical operation of `d.subObject.qtyB` + `d.subObject.qtyC`: ```js {d.subObject.qtyB:add(d.subObject.qtyC)} // 8 (5 + 3) ``` Shorter alternative, with a relative JSON path: ```js {d.subObject.qtyB:add(.qtyC)} // 8 (5 + 3) ``` Multiple dots can be used to access parent attributes with a relative JSON path: ```js {d.subObject.qtyB:add(..qtyA):add(.qtyC)} // 28 (5 + 20 + 3) ``` Here is the same computation using absolute JSON path: ```js {d.subObject.qtyB:add(d.qtyA):add(d.subObject.qtyC)} // 28 (5 + 20 + 3) ``` Read parent objects and their children attributes (there is no limit in depth): ```js {d.subArray[0].qtyE:add(..subObject.qtyC)} // 6 (3 + 3) ``` Access the first item of another table (only positive integers): ```js {d.subObject.qtyB:add(..subArray[0].qtyE)} // 8 (5 + 3) ``` **Known limitations:** Using custom iterators or array filters is **forbidden**: ```js {d.subObject.qtyB:add(..subArray[i].qtyE)} {d.subObject.qtyB:add(..subArray[index, something=3].qtyE)} {d.subObject.qtyB:add(d.subArray[i].qtyE)} {d.subObject.qtyB:add(d.subArray[index].qtyE)} ``` --- #### [Text Formatting](https://carbone.io/documentation/design/formatters/text.md) List of function to manipulate and transform your JSON strings COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :lowerCase v0.12.5+ Lower case all letters _Examples_ ```javascript 'My Car':lowerCase() // "my car" 'my car':lowerCase() // "my car" null:lowerCase() // null 1203:lowerCase() // 1203 ``` ##### :upperCase v0.12.5+ Upper case all letters _Examples_ ```javascript 'My Car':upperCase() // "MY CAR" 'my car':upperCase() // "MY CAR" null:upperCase() // null 1203:upperCase() // 1203 ``` ##### :ucFirst v0.12.5+ Upper case first letter _Examples_ ```javascript 'My Car':ucFirst() // "My Car" 'my car':ucFirst() // "My car" null:ucFirst() // null undefined:ucFirst() // undefined 1203:ucFirst() // 1203 ``` ##### :ucWords v0.12.5+ Upper case the first letter of all words _Examples_ ```javascript 'my car':ucWords() // "My Car" 'My cAR':ucWords() // "My CAR" null:ucWords() // null undefined:ucWords() // undefined 1203:ucWords() // 1203 ``` ##### :print(message) v0.13.0+ Always return the same message if called (sort of "catch all" formatter) | Params | Description | Type | | --- | --- | --- | | message | text to print | String | _Examples_ ```javascript 'My Car':print('hello!') // "hello!" 'my car':print('hello!') // "hello!" null:print('hello!') // "hello!" 1203:print('hello!') // "hello!" ``` ##### :printJSON v4.23.0+ Stringify the object/array _Examples_ ```javascript [{'id':2,'name':'homer'},{'id':3,'name':'bart'}]:printJSON() // "[\n {\"id\": 2, \"name\": \"homer\"},\n {\"id\": 3, \"name\": \"bart\"}\n]" 'my car':printJSON() // "\"my car\"" ``` ##### :unaccent v1.1.0+ Removes accents from text _Examples_ ```javascript 'crème brulée':unaccent() // "creme brulee" 'CRÈME BRULÉE':unaccent() // "CREME BRULEE" 'être':unaccent() // "etre" 'éùïêèà':unaccent() // "euieea" ``` ##### :convCRLF v4.1.0+ It renders carriage return `\r\n` and line feed `\n` into documents instead of printing them as a string. Importante notes: - Feature supported for DOCX, PPTX, ODT, ODP, and ODS files. - ODS supports is experimental for now, contact the support if you find issues. - Since `v3.5.3`, using the `:convCRLF` formatter before `:html` converts `\n` to `
` tags. Usage example: `{d.content:convCRLF:html}` _Examples_ ```javascript // With API options: { // "extension": "odt" // } 'my blue \n car':convCRLF() // "my blue car" 'my blue \r\n car':convCRLF() // "my blue car" // With API options: { // "extension": "docx" // } 'my blue \n car':convCRLF() // "my blue car" 'my blue \r\n car':convCRLF() // "my blue car" ``` ##### :substr(begin, end, wordMode) NEW v4.18.0+ Slice a string with a begin and an end. | Params | Description | Type | | --- | --- | --- | | begin | Zero-based Index at which to begin extraction. | Integer | | end | \[optional\] Zero-based index before which to end extraction | Integer | | wordMode | \[optional\] If `true`, it never cuts words. In such a case: \- `end` must be greater than `begin` and negative values cannot be used. `end - begin` = maximum number of characters per line of text \- A word can only be truncated only if it does not fit in the line. In this case, the word always starts at the beginning of a new line, just like in Word or LibreOffice \- The same line width (end - begin) must be used between successive calls of `substr` to print all the words of the text (no gaps). Ex: `{d.text(0 , 50 , true)}` -> line 1 of 50 characters `{d.text(50 , 100, true)}` -> line 2 of 50 characters `{d.text(100, 150, true)}` -> line 3 of 50 characters `{d.text(150, 200, last)}` -> line 4 of infinite characters \- `last` can be used instead of `true` to print the rest of the text, even if it is longer than the defined line width | Mixed | _Examples_ ```javascript 'foobar':substr(0, 3) // "foo" 'foobar':substr(1) // "oobar" 'foobar':substr(-2) // "ar" 'foobar':substr(2, -1) // "oba" 'abcd efg hijklm':substr(0, 11, true) // "abcd efg " 'abcd efg hijklm':substr(1, 11, true) // "abcd efg " ``` ##### :split(delimiter) NEW v4.12.0+ Split a string using a delimiter It can be used with `arrayJoin('', 1, 2)` to select one specific item of the generated array | Params | Description | Type | | --- | --- | --- | | delimiter | The delimiter | String | _Examples_ ```javascript 'abcdefc12':split('c') // ["ab","def","12"] 1222.1:split('.') // ["1222","1"] 'ab/cd/ef':split('/') // ["ab","cd","ef"] ``` ##### :padl(targetLength, padString) NEW v3.0.0+ Pad the string from the start with another string | Params | Description | Type | | --- | --- | --- | | targetLength | The length of the resulting string once the string has been padded. If the value is less than string length, then string is returned as-is. | number | | padString | The string to pad the current str with. If padString is too long to stay within the targetLength, it will be truncated from the end. The default value is " " | String | _Examples_ ```javascript 'abc':padl(10) // " abc" 'abc':padl(10, 'foo') // "foofoofabc" 'abc':padl(6, '123465') // "123abc" 'abc':padl(8, '0') // "00000abc" 'abc':padl(1) // "abc" ``` ##### :padr(targetLength, padString) NEW v3.0.0+ Pad the string from the end with another string | Params | Description | Type | | --- | --- | --- | | targetLength | The length of the resulting string once the string has been padded. If the value is less than string length, then string is returned as-is. | number | | padString | The string to pad the current str with. If padString is too long to stay within the targetLength, it will be truncated from the end. The default value is " " | String | _Examples_ ```javascript 'abc':padr(10) // "abc " 'abc':padr(10, 'foo') // "abcfoofoof" 'abc':padr(6, '123465') // "abc123" 'abc':padr(8, '0') // "abc00000" 'abc':padr(1) // "abc" ``` ##### :ellipsis(maximum) NEW v4.12.0+ Add "..." if the text is too long | Params | Description | Type | | --- | --- | --- | | maximum | number of characters to print. | Integer | _Examples_ ```javascript 'abcdef':ellipsis(3) // "abc..." 'abcdef':ellipsis(6) // "abcdef" 'abcdef':ellipsis(10) // "abcdef" ``` ##### :prepend(textToPrepend) NEW v4.12.0+ add a prefix to a text | Params | Description | Type | | --- | --- | --- | | textToPrepend | text to prepend | string | _Examples_ ```javascript 'abcdef':prepend('123') // "123abcdef" ``` ##### :append(textToAppend) NEW v4.12.0+ Add a suffix to a text | Params | Description | Type | | --- | --- | --- | | textToAppend | text to append | string | _Examples_ ```javascript 'abcdef':append('123') // "abcdef123" ``` ##### :replace(oldText, newText) NEW v4.12.0+ Replace a text based on a pattern All matches of the pattern (first argument: `oldText`) is replaced by the replacement string (second argument: `newText`). The pattern can only be a string. | Params | Description | Type | | --- | --- | --- | | oldText | old text to replace | string | | newText | new text | string | _Examples_ ```javascript 'abcdef abcde':replace('cd', 'OK') // "abOKef abOKe" 'abcdef abcde':replace('cd') // "abef abe" 'abcdef abcde':replace('cd', null) // "abef abe" 'abcdef abcde':replace('cd', 1000) // "ab1000ef ab1000e" ``` ##### :len v2.0.0+ Returns the length of a string or array. _Examples_ ```javascript 'Hello World':len() // 11 '':len() // 0 [1,2,3,4,5]:len() // 5 [1,'Hello']:len() // 2 ``` ##### :t v4.23.2+ Translate the text using the translation dictionnary ##### :preserveCharRef v4.23.8+ Preserve character reference By default, Carbone removes all forbidden characters before injecting data into XML (e.g., &, >, <, �, ...). As a result, an injected character reference such as `§` ( = `§` ) would be transformed into `&#xa7;` in XML. This formatter prevents that transformation, preserving the character reference. This function is useful for specific XML generation scenarios where the direct character cannot be used (e.g., non-UTF-8 charset), and the character reference must be retained. It accepts numeric (e.g., `d`) and hexadecimal formats (e.g., `򡃯`), in either lower or upper case. ##### :formatR Format a country/region code to a human readable region name _Examples_ ```javascript // With API options: { // "lang": "en-us" // } 'US':formatR() // "United States of America" 'DE':formatR() // "Germany" 'FRA':formatR() // "France" '250':formatR() // "France" '276':formatR() // "Germany" // With API options: { // "lang": "fr-fr" // } 'US':formatR() // "États-Unis d'Amérique" 'DE':formatR() // "Allemagne" 'FRA':formatR() // "France" '250':formatR() // "France" '276':formatR() // "Allemagne" ``` Get inspired by one of our real-life examples: [Summary](/examples/summary/index.md), [Mission Report](/examples/mission-report/index.md), [Grid Layout](/examples/matrix-pptx/index.md) or [Planning with subtotals](/examples/planification-medium/index.md) --- #### [Number Formatting](https://carbone.io/documentation/design/formatters/number.md) List of function to manipulate and transform your JSON numbers COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :formatN(precision) v1.2.0+ Format number according to the locale. Applying a number of decimals depends on the report type: - For ODS/XLSX, the number of decimals has to be formatted based on the text editor. - For the other type of files, the number of decimals depends on the `precision` parameter passed to the formatter. | Params | Description | Type | | --- | --- | --- | | precision | Number of decimals | Number | _Examples_ ```javascript // With API options: { // "lang": "en-us" // } '10':formatN() // "10.000" '1000.456':formatN() // "1,000.456" ``` ##### :round(precision) v1.2.0+ Round a number Same as toFixed(2) but it rounds number correclty `round(1.05, 1) = 1.1` | Params | Description | Type | | --- | --- | --- | | precision | number of decimal | Number | _Examples_ ```javascript 10.05123:round(2) // 10.05 1.05:round(1) // 1.1 ``` ##### :add(value) v1.2.0+ Add two numbers | Params | Description | Type | | --- | --- | --- | | value | Value to add | Number | _Examples_ ```javascript 1000.4:add(2) // 1002.4 '1000.4':add('2') // 1002.4 ``` ##### :sub(value) v1.2.0+ Substract two numbers | Params | Description | Type | | --- | --- | --- | | value | Value to substract | Number | _Examples_ ```javascript 1000.4:sub(2) // 998.4 '1000.4':sub('2') // 998.4 ``` ##### :mul(value) v1.2.0+ Multiply two numbers | Params | Description | Type | | --- | --- | --- | | value | Value to multiply | Number | _Examples_ ```javascript 1000.4:mul(2) // 2000.8 '1000.4':mul('2') // 2000.8 ``` ##### :div(value) v1.2.0+ Divide two numbers | Params | Description | Type | | --- | --- | --- | | value | Value to divide | Number | _Examples_ ```javascript 1000.4:div(2) // 500.2 '1000.4':div('2') // 500.2 ``` ##### :mod(value) NEW v4.5.2+ Compute modulo | Params | Description | Type | | --- | --- | --- | | value | Y | Number | _Examples_ ```javascript 4:mod(2) // 0 3:mod(2) // 1 ``` ##### :abs NEW v4.12.0+ Get absolute value _Examples_ ```javascript -10:abs() // 10 -10.54:abs() // 10.54 10.54:abs() // 10.54 '-200':abs() // 200 ``` ##### :ceil v4.22.8+ Rounds towards closest higher integer number _Examples_ ```javascript 10.05123:ceil() // 11 1.05:ceil() // 2 -1.05:ceil() // -1 ``` ##### :floor v4.22.8+ Rounds towards closest lower integer number. _Examples_ ```javascript 10.05123:floor() // 10 1.05:floor() // 1 -1.05:floor() // -2 ``` ##### :int UNRECOMMENDED FOR USE v1.0.0+ Converts a number to an INT ##### :toEN UNRECOMMENDED FOR USE v1.0.0+ Converts a number with English specifications (decimal separator is '.') ##### :toFixed UNRECOMMENDED FOR USE v1.0.0+ Converts a number into string, keeping only decimals ##### :toFR UNRECOMMENDED FOR USE v1.0.0+ Converts a number with French specifications (decimal separator is ',') Get inspired by one of our real-life examples: [Planning with subtotals](/examples/planification-medium/index.md), [Gallery of images](/examples/gallery-simple/index.md), [Grid Layout](/examples/matrix-pptx/index.md) or [Bank Statement](/examples/bank-statement-simple/index.md) --- #### [Currency Formatting](https://carbone.io/documentation/design/formatters/currency.md) List of function to manipulate and transform your JSON prices COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :formatC(precisionOrFormat, targetCurrency) v1.2.0+ | Params | Description | Type | | --- | --- | --- | | precisionOrFormat | \[optional\] Number of decimal, or specific format \- Integer : change default precision of the currency \- M : print Major currency name without the number \- L : prints number with currency symbol (by default) \- LL : prints number with Major currency name | Number | | targetCurrency | \[optional\] target currency code (upper case). Ex: USD, EUR, ... It overwrites the global option `options.currencyTarget` | String | _Examples_ ```javascript // With API options: { // "lang": "en-us", // "currency": { // "source": "EUR", // "target": "USD", // "rates": { // "EUR": 1, // "USD": 2 // } // } // } '1000.456':formatC() // "$2,000.91" '1000.456':formatC('M') // "dollars" '1':formatC('M') // "dollar" '1000':formatC('L') // "$2,000.00" '1000':formatC('LL') // "2,000.00 dollars" // With API options: { // "lang": "fr-fr", // "currency": { // "source": "EUR", // "target": "USD", // "rates": { // "EUR": 1, // "USD": 2 // } // } // } '1000.456':formatC() // "2 000,91 --TMPL-7-MARK--quot; // With API options: { // "lang": "fr-fr", // "currency": { // "source": "EUR", // "target": "EUR", // "rates": { // "EUR": 1, // "USD": 2 // } // } // } '1000.456':formatC() // "1 000,46 €" ``` ##### :convCurr(target, source) v1.2.0+ Convert from one currency to another Exchange rates are included by default in Carbone but you can provide a new echange rate for one report in `options.currencyRates` of `Carbone.render` or globally with `Carbone.set` `convCurr()` without parameters converts automatically from `options.currencySource` to `options.currencyTarget`. If `options.currencySource` is undefined, no conversion will be performed. | Params | Description | Type | | --- | --- | --- | | target | \[optional\] convert to this currency ('EUR'). By default it equals `options.currencyTarget` | String | | source | \[optional\] currency of source data ('USD'). By default it equals `options.currencySource` | String | _Examples_ ```javascript // With API options: { // "currency": { // "source": "EUR", // "target": "USD", // "rates": { // "EUR": 1, // "USD": 2 // } // } // } 10:convCurr() // 20 1000:convCurr() // 2000 1000:convCurr('EUR') // 1000 1000:convCurr('USD') // 2000 1000:convCurr('USD', 'USD') // 1000 ``` Get inspired by one of our real-life examples: [Ticket](/examples/ticket/index.md), [Invoice](/examples/invoice-simple/index.md) or [Bank statement](/examples/bank-statement-expert/index.md) --- #### [Date Formatting](https://carbone.io/documentation/design/formatters/date.md) List of function to format your JSON dates COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :formatD(patternOut, patternIn) UPDATED v3.0.0+ Format dates. It takes an output date pattern as an argument. Date patterns are available in [this section](#date-formats). It is possible to change the timezone through the option `options.timezone` and the lang through `options.lang`. List of timezones: [https://en.wikipedia.org/wiki/List\_of\_tz\_database\_time\_zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | Params | Description | Type | | --- | --- | --- | | patternOut | output format | String | | patternIn | input format, "ISO 8601" by default | String | _Examples_ ```javascript // With API options: { // "lang": "en-us", // "timezone": "Europe/Paris" // } '20160131':formatD('L') // "01/31/2016" '20160131':formatD('LL') // "January 31, 2016" '20160131':formatD('LLLL') // "Sunday, January 31, 2016 12:00 AM" '20160131':formatD('dddd') // "Sunday" // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '2017-05-10T15:57:23.769561+03:00':formatD('LLLL') // "mercredi 10 mai 2017 14:57" '2017-05-10 15:57:23.769561+03:00':formatD('LLLL') // "mercredi 10 mai 2017 14:57" '20160131':formatD('LLLL') // "dimanche 31 janvier 2016 00:00" '20160131':formatD('dddd') // "dimanche" // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '20160131':formatD('dddd', 'YYYYMMDD') // "dimanche" 1410715640:formatD('LLLL', 'X') // "dimanche 14 septembre 2014 19:27" // With API options: { // "lang": "fr", // "timezone": "Asia/Singapore" // } '20160131':formatD('dddd', 'YYYYMMDD') // "dimanche" 1410715640:formatD('LLLL', 'X') // "lundi 15 septembre 2014 01:27" ``` ##### :addD(amount, unit, patternIn) v3.0.0+ Add a time to a date. Available units: day, week, month, quarter, year, hour, minute, second and millisecond. Units are case insensitive, and support plural and short forms. | Params | Description | Type | | --- | --- | --- | | amount | The amount | Number | | unit | The unit | String | | patternIn | \[optional\] input format, ISO8601 by default | String | _Examples_ ```javascript // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '2017-05-10T15:57:23.769561+03:00':addD('3', 'day') // "2017-05-13T12:57:23.769Z" '2017-05-10 15:57:23.769561+03:00':addD('3', 'month') // "2017-08-10T12:57:23.769Z" '20160131':addD('3', 'day') // "2016-02-03T00:00:00.000Z" '20160131':addD('3', 'month') // "2016-04-30T00:00:00.000Z" '31-2016-01':addD('3', 'month', 'DD-YYYY-MM') // "2016-04-30T00:00:00.000Z" ``` ##### :subD(amount, unit, patternIn) v3.0.0+ Subtract a time to a date. Available units: day, week, month, quarter, year, hour, minute, second and millisecond. Units are case insensitive, and support plural and short forms. | Params | Description | Type | | --- | --- | --- | | amount | The amount | Number | | unit | The unit | String | | patternIn | \[optional\] input format, ISO8601 by default | String | _Examples_ ```javascript // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '2017-05-10T15:57:23.769561+03:00':subD('3', 'day') // "2017-05-07T12:57:23.769Z" '2017-05-10 15:57:23.769561+03:00':subD('3', 'month') // "2017-02-10T12:57:23.769Z" '20160131':subD('3', 'day') // "2016-01-28T00:00:00.000Z" '20160131':subD('3', 'month') // "2015-10-31T00:00:00.000Z" '31-2016-01':subD('3', 'month', 'DD-YYYY-MM') // "2015-10-31T00:00:00.000Z" ``` ##### :startOfD(unit, patternIn) v3.0.0+ Create a date and set it to the start of a unit of time. | Params | Description | Type | | --- | --- | --- | | unit | The unit | String | | patternIn | \[optional\] input format, ISO8601 by default | String | _Examples_ ```javascript // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '2017-05-10T15:57:23.769561+03:00':startOfD('day') // "2017-05-10T00:00:00.000Z" '2017-05-10 15:57:23.769561+03:00':startOfD('month') // "2017-05-01T00:00:00.000Z" '20160131':startOfD('day') // "2016-01-31T00:00:00.000Z" '20160131':startOfD('month') // "2016-01-01T00:00:00.000Z" '31-2016-01':startOfD('month', 'DD-YYYY-MM') // "2016-01-01T00:00:00.000Z" ``` ##### :endOfD(unit, patternIn) v3.0.0+ Create a date and set it to the end of a unit of time. | Params | Description | Type | | --- | --- | --- | | unit | The unit | String | | patternIn | \[optional\] input format, ISO8601 by default | String | _Examples_ ```javascript // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '2017-05-10T15:57:23.769561+03:00':endOfD('day') // "2017-05-10T23:59:59.999Z" '2017-05-10 15:57:23.769561+03:00':endOfD('month') // "2017-05-31T23:59:59.999Z" '20160131':endOfD('day') // "2016-01-31T23:59:59.999Z" '20160131':endOfD('month') // "2016-01-31T23:59:59.999Z" '31-2016-01':endOfD('month', 'DD-YYYY-MM') // "2016-01-31T23:59:59.999Z" ``` ##### :diffD(toDate, unit, patternFromDate, patternToDate) v4.4.0+ Compute the difference between two dates and get an interval. List of available output units for the interval: - `day(s)` or `d` Day of Week (Sunday as 0, Saturday as 6) - `week(s)` or `w` Week of Year - `quarter(s)` or `Q` Quarter - `month(s)` or `M` Month (January as 0, December as 11) - `year(s)` or `y` Year - `hour(s)` or `h` Hour - `minute(s)` or `m` Minute - `second(s)` or `s` Second - `millisecond(s)` or `ms` Millisecond | Params | Description | Type | | --- | --- | --- | | toDate | to date | String, Number | | unit | The output unit: day, week, ... see the list above. Milliseconds by default. | String | | patternFromDate | \[optional\] The pattern of `fromDate`, ISO8601 by default | String | | patternToDate | \[optional\] The pattern of `toDate`, ISO8601 by default | String | _Examples_ ```javascript '20101001':diffD('20101201') // 5270400000 '20101001':diffD('20101201', 'second') // 5270400 '20101001':diffD('20101201', 's') // 5270400 '20101001':diffD('20101201', 'm') // 87840 '20101001':diffD('20101201', 'h') // 1464 '20101001':diffD('20101201', 'weeks') // 8 '20101001':diffD('20101201', 'days') // 61 '2010+10+01':diffD('2010=12=01', 'ms', 'YYYY+MM+DD', 'YYYY=MM=DD') // 5270400000 ``` ##### :convDate(patternIn, patternOut) UNRECOMMENDED FOR USE v1.0.0+ Format dates | Params | Description | Type | | --- | --- | --- | | patternIn | input format | String | | patternOut | output format | String | _Examples_ ```javascript // With API options: { // "lang": "en", // "timezone": "Europe/Paris" // } '20160131':convDate('YYYYMMDD', 'L') // "01/31/2016" '20160131':convDate('YYYYMMDD', 'LL') // "January 31, 2016" '20160131':convDate('YYYYMMDD', 'LLLL') // "Sunday, January 31, 2016 12:00 AM" '20160131':convDate('YYYYMMDD', 'dddd') // "Sunday" 1410715640:convDate('X', 'LLLL') // "Sunday, September 14, 2014 7:27 PM" // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } '20160131':convDate('YYYYMMDD', 'LLLL') // "dimanche 31 janvier 2016 00:00" '20160131':convDate('YYYYMMDD', 'dddd') // "dimanche" ``` Get inspired by one of our real-life examples: [Ticket](/examples/ticket/index.md), [E-Ticket](/examples/eticket/index.md) or [Prescription](/examples/prescription/index.md) ##### Date formats UPDATED v3.0.0+ | Format | Output | Description | | --- | --- | --- | | `X` | 1360013296 | Unix Timestamp | | `x` | 1360013296123 | Unix Millisecond Timestamp | | `YY` | 18 | Two-digit year | | `YYYY` | 2018 | Four-digit year | | `M` | 1-12 | The month, beginning at 1 | | `MM` | 01-12 | The month, 2-digits | | `MMM` | Jan-Dec | The abbreviated month name | | `MMMM` | January-December | The full month name | | `D` | 1-31 | The day of the month | | `DD` | 01-31 | The day of the month, 2-digits | | `d` | 0-6 | The day of the week, with Sunday as 0 | | `dd` | Su-Sa | The minimal name of the day of the week | | `ddd` | Sun-Sat | The short name of the day of the week | | `dddd` | Sunday-Saturday | The name of the day of the week | | `H` | 0-23 | The hour | | `HH` | 00-23 | The hour, 2-digits | | `h` | 1-12 | The hour, 12-hour clock | | `hh` | 01-12 | The hour, 12-hour clock, 2-digits | | `m` | 0-59 | The minute | | `mm` | 00-59 | The minute, 2-digits | | `s` | 0-59 | The second | | `ss` | 00-59 | The second, 2-digits | | `SSS` | 000-999 | The millisecond, 3-digits | | `Z` | +05:00 | The offset from UTC, ±HH:mm | | `ZZ` | +0500 | The offset from UTC, ±HHmm | | `A` | AM PM | | | `a` | am pm | | | `Q` | 1-4 | Quarter | | `Do` | 1st 2nd ... 31st | Day of Month with ordinal | | `k` | 1-24 | The hour, beginning at 1 | | `kk` | 01-24 | The hour, 2-digits, beginning at 1 | | `w` | 1 2 ... 52 53 | Week of year | | `ww` | 01 02 ... 52 53 | Week of year, 2-digits | | `W` | 1 2 ... 52 53 | ISO Week of year | | `WW` | 01 02 ... 52 53 | ISO Week of year, 2-digits | | `wo` | 1st 2nd ... 52nd 53rd | Week of year with ordinal | | `gggg` | 2017 | Week Year | | `GGGG` | 2017 | ISO Week Year | | `z` | EST | Abbreviated named offset | | `zzz` | Eastern Standard Time | Unabbreviated named offset | ###### List of localized formats Because preferred formatting differs based on language, there are a few tokens that can be used to format a date based on report language. There are upper and lower case variations on the same formats. The lowercase version is intended to be the shortened version of its uppercase counterpart. | Format | English Locale | Sample Output | | --- | --- | --- | | `LT` | h:mm A | 8:02 PM | | `LTS` | h:mm:ss A | 8:02:18 PM | | `L` | MM/DD/YYYY | 08/16/2018 | | `LL` | MMMM D, YYYY | August 16, 2018 | | `LLL` | MMMM D, YYYY h:mm A | August 16, 2018 8:02 PM | | `LLLL` | dddd, MMMM D, YYYY h:mm A | Thursday, August 16, 2018 8:02 PM | | `l` | M/D/YYYY | 8/16/2018 | | `ll` | MMM D, YYYY | Aug 16, 2018 | | `lll` | MMM D, YYYY h:mm A | Aug 16, 2018 8:02 PM | | `llll` | ddd, MMM D, YYYY h:mm A | Thu, Aug 16, 2018 8:02 PM | Source: [DayJS](https://day.js.org/docs/en/display/format) ##### Current Date The reserved keyword `{c.now}` returns the current date and time in UTC at the moment the document is generated. If `now` is defined in [`complement`](/documentation/developer/http-api/generate-reports.md), that value will be used instead, allowing you to override the current date. `{c.now}` can be chained with any [date formatter](/documentation/design/formatters/date.md), such as `:formatD`, to display the date in the format of your choice. _Examples_ ```cdata { } ``` ```ctemplate {c.now:formatD('YYYY-MM-DD')} {c.now:formatD('LL')} {c.now:formatD('LLLL')} ``` ```cresult 2025-10-17 October 17, 2025 Friday, October 17, 2025 10:00 AM ``` --- #### [Interval and Duration](https://carbone.io/documentation/design/formatters/interval.md) List of function to manipulate and transform your JSON dates COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v4.0+ ##### :formatI(patternOut, patternIn) v4.1.0+ Format intervals and duration. List of format name : - `human+` - `human` - `millisecond(s)` or `ms` - `second(s)` or `s` - `minute(s)` or `m` - `hour(s)` or `h` - `year(s)` or `y` - `month(s)` or `M` - `week(s)` or `w` - `day(s)` or `d` | Params | Description | Type | | --- | --- | --- | | patternOut | output format: human, human+, milliseconds, seconds, ... | String | | patternIn | \[optional\] input unit: milliseconds, seconds, ... | String | _Examples_ ```javascript // With API options: { // "lang": "en", // "timezone": "Europe/Paris" // } 2000:formatI('second') // 2 2000:formatI('seconds') // 2 2000:formatI('s') // 2 3600000:formatI('minute') // 60 3600000:formatI('hour') // 1 2419200000:formatI('days') // 28 // With API options: { // "lang": "fr", // "timezone": "Europe/Paris" // } 2000:formatI('human') // "quelques secondes" 2000:formatI('human+') // "dans quelques secondes" -2000:formatI('human+') // "il y a quelques secondes" // With API options: { // "lang": "en", // "timezone": "Europe/Paris" // } 2000:formatI('human') // "a few seconds" 2000:formatI('human+') // "in a few seconds" -2000:formatI('human+') // "a few seconds ago" // With API options: { // "lang": "en", // "timezone": "Europe/Paris" // } 60:formatI('ms', 'minute') // 3600000 4:formatI('ms', 'weeks') // 2419200000 // With API options: { // "lang": "en", // "timezone": "Europe/Paris" // } 'P1M':formatI('ms') // 2628000000 'P1Y2M3DT4H5M6S':formatI('hour') // 10296.085 ``` --- #### [Array manipulation](https://carbone.io/documentation/design/formatters/array.md) List of function to manipulate and transform your JSON arrays COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :arrayJoin(separator, index, count) NEW v4.12.0+ Flatten an array of String or Number | Params | Description | Type | | --- | --- | --- | | separator | \[optional\] item separator (`,` by default) | String | | index | \[optional\] select items from this array index. | Number | | count | \[optional\] number of items to select from `index`. `count` can be a negative number to select N items from the end of the array. | Number | _Examples_ ```javascript ['homer','bart','lisa']:arrayJoin() // "homer, bart, lisa" ['homer','bart','lisa']:arrayJoin(' | ') // "homer | bart | lisa" ['homer','bart','lisa']:arrayJoin('') // "homerbartlisa" [10,50]:arrayJoin() // "10, 50" []:arrayJoin() // "" null:arrayJoin() // null {}:arrayJoin() // {} 20:arrayJoin() // 20 undefined:arrayJoin() // undefined ['homer','bart','lisa']:arrayJoin('', 1) // "bartlisa" ['homer','bart','lisa']:arrayJoin('', 1, 1) // "bart" ['homer','bart','lisa']:arrayJoin('', 1, 2) // "bartlisa" ['homer','bart','lisa']:arrayJoin('', 0, -1) // "homerbart" ``` ##### :arrayMap(objSeparator, attSeparator, attributes) v0.12.5+ Flatten an array of objects It ignores nested objects and arrays. If you want to access nested objects, use `aggStr` instead. | Params | Description | Type | | --- | --- | --- | | objSeparator | \[optional\] object separator (`,` by default) | String | | attSeparator | \[optional\] attribute separator (`:` by default) | String | | attributes | \[optional\] list of object's attributes to print | String | _Examples_ ```javascript [{'id':2,'name':'homer'},{'id':3,'name':'bart'}]:arrayMap() // "2:homer, 3:bart" [{'id':2,'name':'homer'},{'id':3,'name':'bart'}]:arrayMap(' - ') // "2:homer - 3:bart" [{'id':2,'name':'homer'},{'id':3,'name':'bart'}]:arrayMap(' ; ', '|') // "2|homer ; 3|bart" [{'id':2,'name':'homer'},{'id':3,'name':'bart'}]:arrayMap(' ; ', '|', 'id') // "2 ; 3" [{'id':2,'name':'homer','obj':{'id':20},'arr':[12,23]}]:arrayMap() // "2:homer" ['homer','bart','lisa']:arrayMap() // "homer, bart, lisa" [10,50]:arrayMap() // "10, 50" []:arrayMap() // "" null:arrayMap() // null {}:arrayMap() // {} 20:arrayMap() // 20 undefined:arrayMap() // undefined ``` ##### :count(start) v1.1.0+ Count and print row number of any array Usage example: `d[i].id:count()` will print a counter of the current row no matter the value of `id` This formatter is replaced internally by `:cumCount` since version v4.0.0 | Params | Description | Type | | --- | --- | --- | | start | Number to start with (default: 1) | String | --- #### [Conditions](https://carbone.io/documentation/design/conditions/overview.md) How to add conditions to your templates? COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v3.0+ ##### Overview There are 3 methods to write conditions: - [Inline conditions](/documentation/design/conditions/inline-conditions.md): to print a word or a small sentence according to your data - [Conditional blocks](/documentation/design/conditions/conditional-blocks.md): to hide or show a part of your document, including multiple Carbone tags, paragraph, tables - [Smart conditional blocks](/documentation/design/conditions/smart-conditions.md): simplified method to show or hide a row, table, paragraph, image Each condition is precede by a formatter that do the logicial test (equal, greater than, ...). Here is the list of all logical operator. **Logicial operators**: - [ifEQ (value)](#ifeq-value) : Matches values that are equal to a specified value, it replaces `ifEqual` - [ifNE (value)](#ifne-value) : Matches all values that are not equal to a specified value - [ifGT (value)](#ifgt-value) : Matches values that are greater than a specified value. - [ifGTE (value)](#ifgte-value) : Matches values that are greater than or equal to a specified value. - [ifLT (value)](#iflt-value) : Matches values that are less than a specified value. - [ifLTE (value)](#iflte-value) : Matches values that are less than or equal to a specified value. - [ifIN (value)](#ifin-value) : Matches any of the values specified in an array or string, it replaces `ifContain` - [ifNIN (value)](#ifnin-value) : Matches none of the values specified in an array or string - [ifEM ()](#ifem) : Matches empty values, string, arrays or objects, it replaces `ifEmpty` - [ifNEM ()](#ifnem) : Matches not empty values, string, arrays or objects - [ifTE (value)](#ifnem) : Matches values where the type equals a specified value - [and (value)](#and-value) : AND operator between two consecutive conditional formatters - [or (value)](#or-value) : (default) OR operator between two consecutive conditional formatters **Followed by one of these formatters:** - [drop (element) / keep (element)](/documentation/design/conditions/smart-conditions.md) : drop or keep some elements automatically if condition is true. - [hideBegin / hideEnd](/documentation/design/conditions/conditional-blocks.md) : hide any part of the document between hideBegin and hideEnd if condition is true - [showBegin / showEnd](/documentation/design/conditions/conditional-blocks.md) : show any part of the document between showBegin and showEnd if condition is true - [show (message)](/documentation/design/conditions/inline-conditions.md) : print a message if a condition is true - [elseShow (message)](/documentation/design/conditions/inline-conditions.md) : print a message if a condition is false No formatters can be chained after `drop`, `keep`, `hideBegin`, `hideEnd`, `showBegin`, `showEnd`. ##### Basic example ```cdata { "val2" : 2, "val5" : 5 } ``` ```ctemplate val2 = {d.val2:ifGT(3):show('high')} val2 = {d.val2:ifGT(3):show('high'):elseShow('low')} val5 = {d.val5:ifGT(3):show('high')} ``` ```cresult val2 = 2 val2 = low val5 = high ``` ##### Chain of conditions If a condition is true, the result isn't passed on to the next **conditional** formatter (`ifEQ`, `show`, `elseShow`, etc.). The value of `show` or `elseShow` is passed directly to the next **non-conditional** formatter (`formatN`, `formatD`, etc.) To clearly see how this works, let's explore how to write a [switch-case condition](/documentation/design/conditions/inline-conditions.md#switch-case). ##### Multiple variables It is possible to test multiple variables with logical operators [and](#and-value) and [or](#or-value): ```cdata { "val2" : 2, "val5" : 5 } ``` ```ctemplate and = {d.val2:ifEQ(1):and(.val5):ifEQ(5):show(OK):elseShow(KO)} or = {d.val2:ifEQ(1):or(.val5):ifEQ(5):show(OK):elseShow(KO)} ``` ```cresult and = KO or = OK ``` The parentheses/priority between logical operators are based on the position of the formatter. Here is the internal representation of the condition for a given Carbone tag: `{d.A:ifEQ(1):and(.B):ifEQ(2):or(.C):ifEQ(3):and(.D):ifEQ(4)}` is equivalent to this: `(((A = 1) AND (B = 2)) OR (C = 3)) AND (D = 4)` ##### :and(value) v2.0.0+ Change the default operator between conditional formatters. For example: `{d.car:ifEQ('delorean'):and(.speed):ifGT(80):show('TravelInTime'):elseShow('StayHere')}` means "if ( (d.car equals 'delorean') AND d.speed is greater than 80 ), then it prints 'TravelInTime', otherwise it prints 'StayHere' | Params | Description | Type | | --- | --- | --- | | value | \[optional\] new value to test | Mixed | ##### :or(value) v2.0.0+ OR is the default operator between conditional formatters. For example: `{d.car:ifEQ('delorean'):or(.speed):ifGT(80):show('TravelInTime'):elseShow('StayHere')}` means "if ( (d.car equals 'delorean') OR d.speed is greater than 80), then it prints 'TravelInTime', otherwise it prints 'StayHere' | Params | Description | Type | | --- | --- | --- | | value | \[optional\] new value to test | Mixed | ##### :ifEM v2.0.0+ Matches empty values, string, arrays or objects (null, undefined, \[\], {}, ...), it replaces `ifEmpty`. _Examples_ ```javascript null:ifEM():show('Result true'):elseShow('Result false') // "Result true" []:ifEM():show('Result true'):elseShow('Result false') // "Result true" {}:ifEM():show('Result true'):elseShow('Result false') // "Result true" '':ifEM():show('Result true'):elseShow('Result false') // "Result true" 0:ifEM():show('Result true'):elseShow('Result false') // "Result false" 'homer':ifEM():show('Result true'):elseShow('Result false') // "Result false" [23]:ifEM():show('Result true'):elseShow('Result false') // "Result false" {'id':3}:ifEM():show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifNEM v2.0.0+ Matches not empty values, string, arrays or objects. _Examples_ ```javascript 0:ifNEM():show('Result true'):elseShow('Result false') // "Result true" 'homer':ifNEM():show('Result true'):elseShow('Result false') // "Result true" [23]:ifNEM():show('Result true'):elseShow('Result false') // "Result true" {'id':3}:ifNEM():show('Result true'):elseShow('Result false') // "Result true" null:ifNEM():show('Result true'):elseShow('Result false') // "Result false" []:ifNEM():show('Result true'):elseShow('Result false') // "Result false" {}:ifNEM():show('Result true'):elseShow('Result false') // "Result false" '':ifNEM():show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifEQ(value) v2.0.0+ Matches all values that are equal to a specified value. It can be combined with other formatters to create conditional content. It returns the initial marker. The state of the condition is not returned. | Params | Description | Type | | --- | --- | --- | | value | value to test | String, Integer | _Examples_ ```javascript 100:ifEQ(100):show('Result true'):elseShow('Result false') // "Result true" 100:ifEQ(101):show('Result true'):elseShow('Result false') // "Result false" 'homer':ifEQ('homer'):show('Result true'):elseShow('Result false') // "Result true" 'homer':ifEQ('bart'):show('Result true'):elseShow('Result false') // "Result false" '':ifEQ(''):show('Result true'):elseShow('Result false') // "Result true" null:ifEQ(100):show('Result true'):elseShow('Result false') // "Result false" null:ifEQ(null):show('Result true'):elseShow('Result false') // "Result true" 0:ifEQ(100):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifNE(value) v2.0.0+ Matches all values that are not equal to a specified value. It can be combined with other formatters to create conditional content. It returns the initial marker. The state of the condition is not returned. | Params | Description | Type | | --- | --- | --- | | value | value to test | String, Integer | _Examples_ ```javascript 100:ifNE(100):show('Result true'):elseShow('Result false') // "Result false" 100:ifNE(101):show('Result true'):elseShow('Result false') // "Result true" 'homer':ifNE('homer'):show('Result true'):elseShow('Result false') // "Result false" 'homer':ifNE('bart'):show('Result true'):elseShow('Result false') // "Result true" '':ifNE(''):show('Result true'):elseShow('Result false') // "Result false" null:ifNE(100):show('Result true'):elseShow('Result false') // "Result true" null:ifNE(null):show('Result true'):elseShow('Result false') // "Result false" 0:ifNE(100):show('Result true'):elseShow('Result false') // "Result true" ``` ##### :ifGT(value) v2.0.0+ Matches values that are greater than a specified value. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript 1234:ifGT(1):show('Result true'):elseShow('Result false') // "Result true" '50':ifGT('-29'):show('Result true'):elseShow('Result false') // "Result true" '32q':ifGT('4q2'):show('Result true'):elseShow('Result false') // "Result true" '1234Hello':ifGT('1'):show('Result true'):elseShow('Result false') // "Result true" '10':ifGT('8Hello1234'):show('Result true'):elseShow('Result false') // "Result true" -23:ifGT(19):show('Result true'):elseShow('Result false') // "Result false" 1:ifGT(768):show('Result true'):elseShow('Result false') // "Result false" 0:ifGT(0):show('Result true'):elseShow('Result false') // "Result false" -2891:ifGT('33Hello'):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifGTE(value) v2.0.0+ Matches values that are greater than or equal to a specified value. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript 50:ifGTE(-29):show('Result true'):elseShow('Result false') // "Result true" 1:ifGTE(1):show('Result true'):elseShow('Result false') // "Result true" 1290:ifGTE(768):show('Result true'):elseShow('Result false') // "Result true" '1234':ifGTE('1'):show('Result true'):elseShow('Result false') // "Result true" -23:ifGTE(19):show('Result true'):elseShow('Result false') // "Result false" 1:ifGTE(768):show('Result true'):elseShow('Result false') // "Result false" '1':ifGTE('1234'):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifLT(value) v2.0.0+ Matches values that are less than a specified value. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript -23:ifLT(19):show('Result true'):elseShow('Result false') // "Result true" 1:ifLT(768):show('Result true'):elseShow('Result false') // "Result true" '1':ifLT('1234'):show('Result true'):elseShow('Result false') // "Result true" '123dsf':ifLT('103123'):show('Result true'):elseShow('Result false') // "Result true" -1299283:ifLT('-2891feihuwf'):show('Result true'):elseShow('Result false') // "Result true" 50:ifLT(-29):show('Result true'):elseShow('Result false') // "Result false" 0:ifLT(0):show('Result true'):elseShow('Result false') // "Result false" 1290:ifLT(768):show('Result true'):elseShow('Result false') // "Result false" '1234':ifLT('1'):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifLTE(value) v2.0.0+ Matches values that are less than or equal to a specified value. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript -23:ifLTE(19):show('Result true'):elseShow('Result false') // "Result true" 1:ifLTE(768):show('Result true'):elseShow('Result false') // "Result true" 5:ifLTE(5):show('Result true'):elseShow('Result false') // "Result true" '1':ifLTE('1234'):show('Result true'):elseShow('Result false') // "Result true" 1290:ifLTE(768):show('Result true'):elseShow('Result false') // "Result false" '1234':ifLTE('1'):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifIN(value) v2.0.0+ Matches any of the values specified in an array or string, it replaces `ifContain`. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript 'car is broken':ifIN('is'):show('Result true'):elseShow('Result false') // "Result true" [1,2,'toto']:ifIN(2):show('Result true'):elseShow('Result false') // "Result true" 'car is broken':ifIN('are'):show('Result true'):elseShow('Result false') // "Result false" [1,2,'toto']:ifIN('titi'):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifNIN(value) v2.0.0+ Matches none of the values specified in an array or string. | Params | Description | Type | | --- | --- | --- | | value | value to test | Integer | _Examples_ ```javascript 'car is broken':ifNIN('are'):show('Result true'):elseShow('Result false') // "Result true" [1,2,'toto']:ifNIN('titi'):show('Result true'):elseShow('Result false') // "Result true" 'car is broken':ifNIN('is'):show('Result true'):elseShow('Result false') // "Result false" [1,2,'toto']:ifNIN(2):show('Result true'):elseShow('Result false') // "Result false" ``` ##### :ifTE(type) NEW v4.4.0+ Tests the type of the operand's value. | Params | Description | Type | | --- | --- | --- | | type | can be "string", "number", "integer", "boolean", "binary", "object", "array" | String | _Examples_ ```javascript 0:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" [23]:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" {'id':3}:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" null:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" []:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" {}:ifTE('string'):show('Result true'):elseShow('Result false') // "Result false" '10':ifTE('string'):show('Result true'):elseShow('Result false') // "Result true" 'homer':ifTE('string'):show('Result true'):elseShow('Result false') // "Result true" '':ifTE('string'):show('Result true'):elseShow('Result false') // "Result true" true:ifTE('boolean'):show('Result true'):elseShow('Result false') // "Result true" false:ifTE('boolean'):show('Result true'):elseShow('Result false') // "Result true" '0':ifTE('boolean'):show('Result true'):elseShow('Result false') // "Result false" 'false':ifTE('boolean'):show('Result true'):elseShow('Result false') // "Result false" '0':ifTE('binary'):show('Result true'):elseShow('Result false') // "Result true" '1':ifTE('binary'):show('Result true'):elseShow('Result false') // "Result true" false:ifTE('binary'):show('Result true'):elseShow('Result false') // "Result true" 'false':ifTE('binary'):show('Result true'):elseShow('Result false') // "Result true" 10.5:ifTE('number'):show('Result true'):elseShow('Result false') // "Result true" '10.5':ifTE('number'):show('Result true'):elseShow('Result false') // "Result false" ``` Get inspired by one of our real-life examples: [Mission Report](/examples/mission-report/index.md), or [Sensor Readings](/examples/sensor-readings/index.md), or [Property advertisement](/examples/real-estate-property-description/index.md), or [Bank Statement](/examples/bank-statement-expert/index.md), or [Product Comparison Table](/examples/product-comparison-table/index.md), or [EditorJS JSON output from HTML WYSIWYG Tool to PDF document](/examples/editorjs/index.md) or [Vehicle Inspection Report](/examples/check-out/index.md) --- #### [Inline Conditions](https://carbone.io/documentation/design/conditions/inline-conditions.md) How to conditionally print a text? COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v3.0+ ##### :show(text) / :elseShow(text) If the condition is true, `:show` prints the text between parentheses and the text is passed to the next formatter, bypassing all conditional formatters (ifEQ, show, ...). Here are some useful examples ```cdata { "val2" : 2, "val5" : 5 } ``` ```ctemplate val2 = {d.val2:ifGT(3):show('high')}val5 = {d.val5:ifGT(3):show('high')}val2 = {d.val2:ifGT(3):show('high'):elseShow('low')} ``` ```cresult val2 = 2val5 = highval2 = low ``` ##### Switch case If the condition is not true, the value is passed to the next formatter. This allows you to easily create a switch-case structure like this: ```cdata { "val1" : 1, "val2" : 2, "val3" : 3 } ``` ```ctemplate val1 = {d.val1:ifEQ(1):show(A):ifEQ(2):show(B):elseShow(C)}val2 = {d.val2:ifEQ(1):show(A):ifEQ(2):show(B):elseShow(C)}val3 = {d.val3:ifEQ(1):show(A):ifEQ(2):show(B):elseShow(C)} ``` ```cresult val1 = Aval2 = Bval3 = C ``` Syntax alternative: `{d.val1:ifEQ(1):show(A):or(d.val1):ifEQ(2):show(B):elseShow(C)}` Get inspired by one of our real-life examples: [Check-out](/examples/check-out/index.md) or [EditorJS JSON output from HTML WYSIWYG Tool to PDF document](/examples/editorjs/index.md) ##### With array filters Array filter can be used to show or hide a Carbone tag. Carbone hides only one tag if the condition is not repeated for all tags in the loop. ```cdata [ { "name": "John", "age": 20 }, { "name": "Eva", "age": 18 }, { "name": "Bob", "age": 25 }, { "name": "Charly", "age": 30 } ] ``` **Template:** | People | | --- | | {d[i].name}: {d[i, age > 19].age} | | {d[i+1].name} | **Result:** | People | | --- | | John: 20 | | Eva: | | Bob: 25 | | Charly: 30 | Get inspired by one of our real-life examples: [Planning with subtotals](/examples/planification-medium/index.md) ##### :ifEmpty(textIfTrue, continueOnSuccess) v0.12.5+ Test if data is empty (null, undefined, \[\], {}, ...). The new formatter `:ifEM:show` should be used instead of this one. | Params | Description | Type | | --- | --- | --- | | textIfTrue | textIfTrue to print if JSON data is empty | String | | continueOnSuccess | \[optional\], if true, next formatter will be called even if the condition is true | Boolean | _Examples_ ```javascript null:ifEmpty('D'oh!') // "D'oh!" []:ifEmpty('D'oh!') // "D'oh!" {}:ifEmpty('D'oh!') // "D'oh!" '':ifEmpty('D'oh!') // "D'oh!" 0:ifEmpty('D'oh!') // 0 'homer':ifEmpty('D'oh!') // "homer" [23]:ifEmpty('D'oh!') // [23] {'id':3}:ifEmpty('D'oh!') // {"id":3} ``` ##### :ifEqual(value, textIfTrue, continueOnSuccess) v0.13.0+ Test if a value equals a variable. The new formatter `:ifEQ:show` should be used instead of this one. | Params | Description | Type | | --- | --- | --- | | value | value to test | String, Integer, Boolean | | textIfTrue | message to print if the value equals JSON data | String | | continueOnSuccess | \[optional\], if true, next formatter will be called even if the condition is true | Boolean | _Examples_ ```javascript 100:ifEqual(100, 'bingo') // "bingo" 100:ifEqual(101, 'bingo') // 100 'homer':ifEqual('homer', 'bingo') // "bingo" 'homer':ifEqual('bart', 'bingo') // "homer" '':ifEqual('', 'bingo') // "bingo" null:ifEqual(100, 'bingo') // null null:ifEqual(null, 'bingo') // "bingo" 0:ifEqual(100, 'bingo') // 0 ``` ##### :ifContain(value, textIfTrue, continueOnSuccess) v0.13.0+ Test if a string or an array contains a value. The new formatter `:ifIN:show` should be used instead of this one. | Params | Description | Type | | --- | --- | --- | | value | value to search | String, Integer, Boolean | | textIfTrue | message to print if JSON data contains the value | String | | continueOnSuccess | \[optional\], if true, next formatter will be called even if the condition is true | Boolean | _Examples_ ```javascript 'your beautiful eyes':ifContain('beauti', 'bingo') // "bingo" 'your beautiful eyes':ifContain('leg', 'bingo') // "your beautiful eyes" 'your beautiful eyes':ifContain('eyes', 'bingo') // "bingo" '':ifContain('eyes', 'bingo') // "" 'your beautiful eyes':ifContain('', 'bingo') // "bingo" [100,120,20]:ifContain(120, 'bingo') // "bingo" [100,120,20]:ifContain(99, 'bingo') // [100,120,20] ['your','eyes']:ifContain('eyes', 'bingo') // "bingo" []:ifContain('eyes', 'bingo') // [] ``` --- #### [Conditional blocks](https://carbone.io/documentation/design/conditions/conditional-blocks.md) How to hide or show a part of your document? COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ ##### :showBegin / :showEnd Show a text block between showBegin and showEnd if a condition is true. It is recommended to only use break-lines (`shift` + `enter`) between `showBegin` and `showEnd` ([learn more](https://help.carbone.io/en-us/article/why-conditional-formatters-are-adding-empty-lines-showbeginhidebegin-1y56nmc/)). **NEW**: Use the [drop/keep](/documentation/design/conditions/smart-conditions.md) formatter if you are hiding a paragraph, table row, image, chart or shape. ```cdata { "toBuy" : true } ``` ```ctemplate Banana{d.toBuy:ifEQ(true):showBegin} Apple Pineapple {d.toBuy:showEnd}Grapes ``` ```cresult Banana Apple Pineapple Grapes ``` Get inspired by one of our real-life examples: [Mission Report](/examples/mission-report/index.md) ##### :hideBegin / :hideEnd Hide text block between hideBegin and hideEnd if a condition is true. It is recommended to only use break-lines (`shift` + `enter`) between `hideBegin` and `hideEnd` ([learn more](https://help.carbone.io/en-us/article/why-conditional-formatters-are-adding-empty-lines-showbeginhidebegin-1y56nmc/)). **NEW**: Use the [drop/keep](/documentation/design/conditions/smart-conditions.md) formatter if you are removing a paragraph, table row, image, chart or shape. ```cdata { "toBuy" : true } ``` ```ctemplate Banana{d.toBuy:ifEQ(true):hideBegin} Apple Pineapple {d.toBuy:hideEnd}Grapes ``` ```cresult Banana Grapes ``` --- #### [Smart conditional blocks](https://carbone.io/documentation/design/conditions/smart-conditions.md) Show or hide an entire table, paragraph, ..., row, column with one simple Carbone tag ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v4.0+ ##### Why is it smart? Why should you use smart conditional blocks instead of `hideBegin/hideEnd` or `showBegin/showEnd` or array filters? - Shorter syntax: You only need to write one Carbone tag instead of two (Begin + End). - Simpler to use: In some situations, it can be tricky to place hideBegin/hideEnd formatters in the document, such as when hiding a row of a table. - Cleaner output: The targeted element is removed without leaving whitespace. Compatible with DOCX, ODT, ODS, ODP, XLSX, PPTX, and HTML templates. A Carbone tag using `:drop` or `:keep` does not print anything in the generated report. The tag is removed when executed. ##### :drop/:keep(element) Use the `:drop` formatter to remove elements from a document if the specified condition is true. The `:keep` formatter works oppositely. It retains elements if the condition is true. | Element | Description | Accepted tag position | | --- | --- | --- | | `row` | Drop or keep a table row | In a table row | | `col` | Drop or keep an entire table column | In any cell of the target column | | `p` | Drop or keep a paragraph | Within a paragraph | | `img` | Drop or keep an image | Image title, description, or alt text | | `table` | Drop or keep a table | Inside a table cell | | `chart` | Drop or keep a chart | Alternative text of the chart | | `shape` | Drop or keep a shape | Title, description, or alt text | | `slide` | Drop or keep a slide | Inside a text box (**ODP** only) | | `item` | Drop or keep a list item | Within a list item (**ODP/ODT** only) | | `sheet` | Drop or keep a sheet | Inside a table cell (**ODS** only) | | `h` | Drop or keep a heading or custom style element | Applies within the heading element (**ODT** only) | | `div` | Drop or keep a div block | Within a div element (**HTML** only) | | `span` | Drop or keep a span element | Within a span element (**HTML** only) | A second parameter can be used with `p` and `row` to drop or keep the next **N** paragraphs or rows. This is not supported for `col`. Example: - `{d.text:ifEM:drop(p, 3)}`: Drops the current paragraph and the next two paragraphs if `d.text` is empty. - `{d.text:ifEM:drop(row, 3)}`: Drops the current row and the next two rows if `d.text` is empty. **Known limitation:** - `ODS` templates only support dropping/keeping `col`, `row`, `img`, and `sheet`. - `XLSX` templates only support dropping/keeping `row` and `col`. - `PPTX` templates only support dropping/keeping `col`, `row`, `img`, `p`, `shape`, `chart`, and `table`. - `HTML` templates only support dropping/keeping `table`, `col`, `row`, `p`, `div`, and `span`. ##### Examples _Drop rows of the table if an item name contains 'Falcon':_ ```cdata [ { "name": "Falcon 9" }, { "name": "Model S" }, { "name": "Model 3" }, { "name": "Falcon Heavy" } ] ``` **Template:** | Planes | | --- | | {d[i].name}{d[i].name:ifIN('Falcon'):drop(row)} | | {d[i+1].name} | **Result:** | Planes | | --- | | Model S | | Model 3 | _Delete the entire table if the array length is lower than 6:_ ```cdata [ { "name": "Bob" } ] ``` **Template:** | Planes {d:len:ifLT(6):drop(table)} | | --- | | {d[i].name} | | {d[i+1].name} | ```cresult ``` _Drop an entire column if a field is empty:_ ```cdata { "showDiscount": false, "items": [ { "name": "Falcon 9", "price": 1000, "discount": 100 }, { "name": "Model S", "price": 800, "discount": 0 } ]} ``` **Template:** | Product | Price | Discount | | --- | --- | --- | | {d.items[i].name} | {d.items[i].price} | {d.showDiscount:ifEQ(false):drop(col)} {d.items[i].discount} | | {d.items[i+1].name} | {d.items[i+1].price} | | **Result:** | Product | Price | | --- | --- | | Falcon 9 | 1000 | | Model S | 800 | **Best practices:** - Place the marker in any single row — Carbone detects the column and applies the condition to all rows automatically. - Only one `drop(col)` per column is allowed — do not repeat the marker in other rows of the same column. - Nested tables inside cells of the same row are not affected. **Limitations:** - Merged cells are not supported (planned). ##### Tutorials - [Tutorial to drop paragraphs](https://help.carbone.io/en-us/article/how-to-hide-a-paragraph-conditionally-1ocoem4/?bust=1662368759940) - [Tutorial to drop table rows](https://help.carbone.io/en-us/article/how-to-hide-a-table-row-conditionally-dqqf22/) - [Tutorial to drop images with MS Word or LibreOffice](https://help.carbone.io/en-us/article/how-to-hide-an-image-if-it-is-emptyblank-f09687/) - [Tutorial to drop charts](https://help.carbone.io/en-us/article/how-to-hide-charts-conditionally-miaavk/) Get inspired by one of our real-life examples: [EditorJS JSON output from HTML WYSIWYG Tool to PDF document](/examples/editorjs/index.md) --- #### [Simple Mathematics](https://carbone.io/documentation/design/computation/simple-mathematics.md) Simple addition, substraction, multiplications, division COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v4.0+ ##### With formatters Here is the list of formatters that can be used to perform simple operations: - [add (value)](/documentation/design/formatters/number.md#add-value) : Addition - [mul (value)](/documentation/design/formatters/number.md#mul-value) : Multiplication - [sub (value)](/documentation/design/formatters/number.md#sub-value) : Subtraction - [div (value)](/documentation/design/formatters/number.md#div-value) : Division - [mod (value)](/documentation/design/formatters/number.md#mod-value) : Modulo - [abs (value)](/documentation/design/formatters/number.md#abs) : Get absolute value ##### Mathematical formula Formatters `add()`, `mul()`, `sub()` and `div()` accept simple mathematical expressions inside parenthesis. - Only mathematical operators `+, *, -, /` are allowed, **without parenthesis** - Multiplication and division operators (`*`, `/`) has higher precedence than the addition/subtraction operator (`+`, `-`) and thus will be evaluated first. **Examples:** ```cdata { "val" : 1, "otherQty" : 2, "vat" : 0.5, "sub" : { "price" : 100 } } ``` ```ctemplate {d.val:add(.otherQty + .vat * d.sub.price - 10 / 2)} ``` ```cresult 48 ``` ##### Arithmetic precision By default, calculations use double-precision floating-point numbers based on the 64-bit IEEE-754 standard. To avoid [floating-point precision issues](https://floating-point-gui.de/), as of version 4.22.4, you can use the option `{o.useHighPrecisionArithmetic=true}` within a template to enable arbitrary-precision decimal arithmetic for mathematical operations. When this option is included in the template, the formatters `:add`, `:sub`, `:mul`, `:div`, `:formatC`, and `:formatN` will maintain full decimal precision, up to a maximum of 20 decimal places. --- #### [Aggregators](https://carbone.io/documentation/design/computation/aggregation.md) How to perform calculations on array data? ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v4.0+ ##### Overview An aggregate formatter processes a set of values and returns a single aggregated result. It can be used as a [standalone expression](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) or within [loops](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby-aggregate-and-loop) to calculate [custom groupings](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby-aggregate-for-independent-groups), such as [sub-totals](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby-aggregator-with-nested-arrays) or [cumulative totals](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby-aggregate-and-loop). The following aggregators are available: - [aggSum](#learn-with-aggsum-partitionby): Calculates the sum of all values in a dataset. - [aggAvg](#aggavg-partitionby): Computes the average of values in a dataset. - [aggMin](#aggmin-partitionby): Finds the minimum value in a dataset. - [aggMax](#aggmax-partitionby): Finds the maximum value in a dataset. - [aggCount](#aggcount-partitionby): Counts the total number of items in a dataset. - [aggCountD](#aggcountd-partitionby): Counts the number of distinct items in a dataset. - [aggStr](#aggstr-separator-partitionby): Concatenates all strings in a dataset, using a specified separator. - [aggStrD](#aggstrd-separator-partitionby): Concatenates distinct strings in a dataset, using a specified separator. - [cumSum](#cumsum-partitionby): Calculates cumulative sums (running totals) as the dataset progresses through a series. - [cumCount](#cumcount-partitionby): Assigns a sequential integer to each row in a list. - [cumCountD](#cumcountd-partitionby): Assigns a sequential integer to each distinct item in a list. Aggregators have a limitation when accessing a value within a sub-object, such as `{d[].sub.qty}`. To handle this issue, use `:print` formatter to aggregate values coming from sub-objects. For example: `{d[]:print(.sub.qty):aggSum}` ##### Learn with :aggSum(partitionBy) Here is an example using `:aggSum`, which calculates and returns the total sum of all values in a dataset. Carbone automatically iterates through the array and calculates the sum of all its elements if the `[]` brackets are left empty. [Array filters](/documentation/design/repetitions/filtering.md) can be used to reduce the dataset by applying specific conditions. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Total: {d.cars[].qty:aggSum} Total with filters: {d.cars[sort>1].qty:aggSum} Total with multiplication: {d.cars[sort>1].qty:mul(.sort):aggSum:formatC} ``` ```cresult Total: 21 Total with filters: 19 Total with multiplication: 79.00 € ``` ###### Aggregate and Loop Perform aggregation and looping simultaneously. You can calculate and display the cumulative sum of values using `:cumSum` instead of `:aggSum`. This allows you to track the running total as you iterate through the data. ```cdata [ { "brand":"Lexus" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Venturi" , "qty":10 } ] ``` **Template:** | Brand | Total | Cumulative | | --- | --- | --- | | {d[i].brand} | {d[i].qty:aggSum} | {d[i].qty:cumSum} | | {d[i+1].brand} | | | **Result:** | Brand | Total | Cumulative | | --- | --- | --- | | Lexus | 21 | 1 | | Faraday | 21 | 5 | | Venturi | 21 | 8 | | Faraday | 21 | 10 | | Aptera | 21 | 11 | | Venturi | 21 | 21 | ###### Aggregate for independent groups Carbone aggregators support one or more parameters that allow you to divide data into separate groups (or "partitions") for independent calculations. This functionality is similar to the `PARTITION BY` clause in SQL. By specifying grouping parameters, you can ensure that the aggregation is performed independently for each defined group, making it easier to generate structured reports with grouped totals, averages, or other metrics. ```cdata [ { "brand":"Lexus" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Venturi" , "qty":10 } ] ``` **Template:** | Brand | Total by brand | | --- | --- | | {d[i].brand} | {d[i].qty:aggSum(.brand)} | | {d[i+1].brand} | | **Result:** | Brand | Total by brand | | --- | --- | | Lexus | 1 | | Faraday | 6 | | Venturi | 13 | | Faraday | 6 | | Aptera | 1 | | Venturi | 13 | ###### Aggregator with nested arrays Calculating subtotals for nested arrays. ```cdata [ { "country" : "France", "cities" : [ { "id" : "Paris" , "cars" : 100 }, { "id" : "Nantes" , "cars" : 50 }, { "id" : "Pouzauges" , "cars" : 1 } ] }, { "country" : "Italy", "cities" : [ { "id" : "Rome" , "cars" : 20 }, { "id" : "Venise" , "cars" : 2 } ] } ] ``` **Template:** | Brand | Total per city | | --- | --- | | {d[i].country} | {d[i].cities[].cars:aggSum} | | {d[i+1].country} | | **Result:** | Brand | Total per city | | --- | --- | | France | 151 | | Italy | 22 | Get inspired by one of our real-life examples: [Invoice](/examples/invoice-simple/index.md), [Bank Statement](/examples/bank-statement-expert/index.md), [Store Inventory](/examples/shoes/index.md) or [Planning with subtotals](/examples/planification-medium/index.md) ##### :aggAvg(partitionBy) The `:aggAvg` aggregator calculates and returns the average of a dataset. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Average: {d.cars[].qty:aggAvg} Average with filters: {d.cars[sort>1].qty:aggAvg} Average with multiplication: {d.cars[sort>1].qty:mul(.sort):aggAvg:formatC} ``` ```cresult Average: 3.5 Average with filters: 4.75 Average with multiplication: 19.75 € ``` Get inspired by one of our real-life examples: [Sensor Readings](/examples/sensor-readings/index.md) ##### :aggMin(partitionBy) The `:aggMin` aggregator returns the minimum value of a dataset. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Minimum: {d.cars[].qty:aggMin} Minimum with filters: {d.cars[sort>1].qty:aggMin} ``` ```cresult Minimum: 1 Minimum with filters: 2 ``` ##### :aggMax(partitionBy) The `:aggMax` aggregator returns the maximum value of a dataset. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Maximum: {d.cars[].qty:aggMax} Maximum with filters: {d.cars[sort>1].qty:aggMax} ``` ```cresult Maximum: 10 Maximum with filters: 10 ``` ##### :aggCount(partitionBy) The `:aggCount` aggregator returns the number of items of a dataset. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. It counts items regardless of the value passed on the left side of the formatter (e.g., `{d[i].qty}` in the example). The value is ignored by the formatter. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Number of items: {d.cars[].qty:aggCount} Number of items with filters: {d.cars[sort>1].qty:aggCount} ``` ```cresult Number of items: 6 Number of items with filters: 4 ``` ##### :aggCountD(partitionBy) The `:aggCountD` aggregator calculates and returns the number of **Distinct** items in a dataset. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Number of brands: {d.cars[].brand:aggCountD} ``` ```cresult Number of brands: 4 ``` ##### :aggStr(separator, partitionBy) The `:aggStr` aggregator concatenates all values in a dataset, separated by `", "` (the default `separator`). For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Cars list: {d.cars[].brand:aggStr} {d.cars[].brand:aggStr(' | ')} ``` ```cresult Cars list: Lexus, Faraday, Venturi, Faraday, Aptera, Venturi Lexus | Faraday | Venturi | Faraday | Aptera | Venturi ``` ##### :aggStrD(separator, partitionBy) The `:aggStrD` aggregator concatenates all **Distinct** values in a dataset, separated by `", "` (the default `separator`). For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata { "cars": [ { "brand":"Lexus" , "qty":1 , "sort":1 }, { "brand":"Faraday" , "qty":4 , "sort":4 }, { "brand":"Venturi" , "qty":3 , "sort":3 }, { "brand":"Faraday" , "qty":2 , "sort":2 }, { "brand":"Aptera" , "qty":1 , "sort":1 }, { "brand":"Venturi" , "qty":10, "sort":5 } ] } ``` ```ctemplate Cars list: {d.cars[].brand:aggStrD} {d.cars[].brand:aggStrD(' | ')} ``` ```cresult Cars list: Lexus, Faraday, Aptera, Venturi Lexus | Faraday | Aptera | Venturi ``` ##### :cumSum(partitionBy) The `:cumSum` formatter, also called a running total, calculates the cumulative sum of data by continuously adding values as it moves through the series. ```cdata [ { "brand":"Lexus" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Venturi" , "qty":10 } ] ``` **Template:** | Brand | Running total | | --- | --- | | {d[i].brand} | {d[i].qty:cumSum} | | {d[i+1].brand} | | **Result:** | Brand | Running total | | --- | --- | | Lexus | 1 | | Faraday | 5 | | Venturi | 8 | | Faraday | 10 | | Aptera | 11 | | Venturi | 21 | Get inspired by one of our real-life examples: [Bank Statement](/examples/bank-statement-expert/index.md), or [Planning with subtotals](/examples/planification-medium/index.md) ##### :cumCount(partitionBy) The `:cumCount` formatter returns a sequential integer to each row in a list. A dynamic variable, `partition`, can be passed to `:cumCount(partition)`. This ensures the row numbering resets to 1 for each partition and increments sequentially within that partition. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. It counts items regardless of the value passed on the left side of the formatter (e.g., `{d[i].qty}` in the example). The value is ignored by the formatter. ```cdata [ { "brand":"Lexus" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Venturi" , "qty":10 } ] ``` **Template:** | Brand | Count | Count by brand | | --- | --- | --- | | {d[i].brand} | {d[i].qty:cumCount} | {d[i].qty:cumCount(.brand)} | | {d[i+1].brand} | | | **Result:** | Brand | Count | Count by brand | | --- | --- | --- | | Lexus | 1 | 1 | | Faraday | 2 | 1 | | Faraday | 3 | 2 | | Faraday | 4 | 3 | | Venturi | 5 | 1 | | Venturi | 6 | 2 | | Aptera | 7 | 1 | | Venturi | 8 | 3 | Get inspired by one of our real-life examples: [Bank Statement](/examples/bank-statement-simple/index.md) or [Gallery of images](/examples/gallery-simple/index.md) ##### :cumCountD(partitionBy) The `:cumCountD` counts the number of **Distinct** items in a list. For more details on using aggregators and partitioning, refer to the [examples](/documentation/design/computation/aggregation.md#learn-with-aggsum-partitionby) provided for `:aggSum`. ```cdata [ { "brand":"Lexus" , "qty":1 }, { "brand":"Faraday" , "qty":4 }, { "brand":"Faraday" , "qty":2 }, { "brand":"Venturi" , "qty":3 }, { "brand":"Aptera" , "qty":1 }, { "brand":"Venturi" , "qty":10 } ] ``` **Template:** | Brand | Count | | --- | --- | | {d[i].brand} | {d[i].brand:cumCountD} | | {d[i+1].brand} | | **Result:** | Brand | Count | | --- | --- | | Lexus | 1 | | Faraday | 2 | | Faraday | 2 | | Venturi | 3 | | Aptera | 4 | | Venturi | 4 | ##### :aggSum:cumSum The `:aggSum` and `:cumSum` formatters can be used together in specific cases, such as calculating the total number of cars per country while also displaying a running total. ```cdata [ { "country" : "France", "cities" : [ { "id" : "Paris" , "cars" : 100 }, { "id" : "Nantes" , "cars" : 50 }, { "id" : "Pouzauges" , "cars" : 1 } ] }, { "country" : "Italy", "cities" : [ { "id" : "Rome" , "cars" : 20 }, { "id" : "Venise" , "cars" : 2 } ] } ] ``` **Template:** | Brand | Running total | | --- | --- | | {d[i].country} | {d[i].cities[].cars:aggSum:cumSum} | | {d[i+1].country} | | **Result:** | Brand | Running total | | --- | --- | | France | 151 | | Italy | 173 | --- #### [Store and transform](https://carbone.io/documentation/design/computation/store-and-transform.md) Store, modify, and transform JSON to simplify your templates ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v5.0+ This feature is officially available in Carbone v5. However, you can activate it in v4 by inserting `{o.preReleaseFeatureIn=4022011}` into your template. For more details, refer to the [Files and features documentation](/documentation/design/overview/version-lifecycle.md#pre-release-features). ##### Store values -> :set(absolutePath) The `:set(absolutePath)` formatter allows you to store values in data. [absolutePath](/documentation/design/formatters/overview.md#dynamic-parameters) specifies the destination where the value will be stored. - It is good practice to store new variables in the **complement object** (`{c.}`) instead of the **data object** (`{d.}`). This helps maintain a cleaner and more organized data structure. - The execution order of multiple Carbone tags using the `:set` formatter is guaranteed within the same section of a document (e.g., body, header, footer, or text box). As a result, you can create new variables that depend on previously created ones. When one `:set` expression is used to create another, the new expression must be defined after or below the existing one. - The `:set` formatter overwrites existing values in the data. - Only alphanumeric characters are allowed for newly created JSON attributes, objects, or arrays (no spaces or special characters such as commas, parentheses, etc.). A Carbone tag using `:set` does not print anything in the generated report. The tag is removed when executed. A loop with iterators `[i]` cannot be used in combination with `:set`. **Example: store the sum in c.mySum** ```cdata { "cars": [ { "qty" : 1 }, { "qty" : 4 } ] } ``` ```ctemplate {d.cars[].qty:aggSum:set(c.mySum)} Print stored value: {c.mySum} ``` ```cresult Print stored value: 5 ``` **Example with order of execution:** ```cdata { "price" : 1 } ``` ```ctemplate {d.price:add(10):set(d.total1)} {d.total1:add(20):set(d.total2)} Result: {d.total2} ``` ```cresult Result: 31 ``` ##### Modify JSON -> :set(.relativePath) Add or modify attributes in your data using a [JSON relative path](/documentation/design/formatters/overview.md#dynamic-parameters). In this example, the Carbone tags `{d.cars[].qty:append(' tyres'):set(.newInfo)}` adds the attribute "newInfo" to all objects in the "cars" array. - `d.cars[]`: Loops over all cars. - `.qty`: Prints the `qty` value. - `append(' tyres')`: Appends the string " tyres" to `qty`. - `:set(.newInfo)`: Stores the result in the new attribute `newInfo` in each object of `cars` ```cdata { "cars": [ { "qty" : 1 }, { "qty" : 4 } ] } ``` ```ctemplate {d.cars[].qty:append(' tyres'):set(.newInfo)} {d:printJSON} ``` ```cresult { "cars": [ {"qty": 1, "newInfo": "1 tyres"}, {"qty": 4, "newInfo": "4 tyres"} ] } ``` ##### Transform JSON -> :set(absolutePath\[\]) Generate a completely new JSON structure with `:set` formatter. Here is an overview of the syntax: `{ d.sourceArray[arrayFilter] :set( c.destinationArray[searchExpression] )}` - `d.sourceArray` : The existing array - `[arrayFilter]` : Optional array filters. The `i` iterator is not allowed with the `:set` formatter. Leave empty `[]` to traverse all data. - `:set`: The formatter - `c.destinationArray` : The newly created array in `c.`. Only alphanumeric characters are allowed for newly created JSON attributes, objects, or arrays. - `[searchExpression]` : An optional condition used to find and merge items with existing ones in the newly created array. It works like an inner join expression in SQL. An empty `searchExpression` is accepted only for the last created array in the `:set()` expression. In this case, all items are aggregated without searching for existing ones. Only the equality `=` operator is allowed in the `searchExpression`, and exactly one expression is permitted within `[]`. This `searchExpression` works only on newly created arrays by Carbone, not on existing arrays in the original JSON data passed when calling Carbone. **Learn the basics with simple examples:** - [Clone arrays](#transform-json-set-absolutepath-cloning-arrays) - [Selective array cloning](#transform-json-set-absolutepath-selective-array-cloning) - [Merge arrays](#transform-json-set-absolutepath-merge-arrays) **Advanced usage with search/join expressions:** - [Distinct merge](#transform-json-set-absolutepath-distinct-merge) - [Group data in nested arrays](#transform-json-set-absolutepath-group-data-in-nested-arrays) ###### Cloning arrays `{ d.myArray[] :set( c.new[] )}` - `d.myArray[]` : Loops over the existing array and retrieves its content - `:set(c.new[])`: Injects the content into a new array located at `{c.new}`. **Array of objects example:** ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" } ] } ``` ```ctemplate {d.myArray[]:set(c.new[])} {c:printJSON} ``` ```cresult { "new": [ {"country": "A", "city": "1A"}, {"country": "A", "city": "2A"}, {"country": "B", "city": "1B"} ] } ``` **Array of string example:** ```cdata { "myArray" : [ "A", "B", "C"] } ``` ```ctemplate {d.myArray[]:set(c.new[])} {c:printJSON} ``` ```cresult {"new": ["A", "B", "C"]} ``` ###### Selective array cloning In this example, only the `country` attribute is injected into the new array. As you can see, Carbone creates an array of string or an array of objects according to destination `c.newArr1[]` or `c.newArr2[].country`. ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" } ] } ``` ```ctemplate {d.myArray[].country:set(c.newArr1[])} {d.myArray[].country:set(c.newArr2[].country)} {c:printJSON} ``` ```cresult { "newArr1": ["A", "A", "B"], "newArr2": [ {"country": "A"}, {"country": "A"}, {"country": "B"} ] } ``` ###### Merge arrays ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" } ], "myArray2" : [ { "country" : "C", "city" : "1C" }, { "country" : "A", "city" : "3A" } ] } ``` ```ctemplate {d.myArray[]:set(c.newArr[])} {d.myArray2[]:set(c.newArr[])} {c:printJSON} ``` ```cresult { "newArr": [ {"country": "A", "city": "1A"}, {"country": "A", "city": "2A"}, {"country": "B", "city": "1B"}, {"country": "C", "city": "1C"}, {"country": "A", "city": "3A"} ] } ``` ###### Distinct Merge Carbone accepts a **search expression** enclosed in square brackets to determine how to add new items. If an item in the new array **matches the condition**, the **new item is merged** with the **existing one** `{ d.myArray[] :set( c.new[ country = .country] )}` - `d.myArray[]` : Loops over the existing array and return its content - `:set(c.new[ country = .country ])`: Injects the content into a new array - `c.new` is the newly created array in `c.` - `[country=.country]` is a search expression used to find existing items that match the criteria - `country` is a relative path to the new object inside `c.new`. Equivalent to `c.new[].country` - `.country` is a relative path in the currently visited array. Equivalent to `d.myArray[].country` ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" } ], "myArray2" : [ { "country" : "C", "city" : "1C" }, { "country" : "A", "city" : "3A" } ] } ``` ```ctemplate {d.myArray[]:set(c.new[country=.country])} {d.myArray2[]:set(c.new[country=.country])} {c:printJSON} ``` ```cresult { "new": [ {"country": "A", "city": "3A"}, {"country": "B", "city": "1B"}, {"country": "C", "city": "1C"} ] } ``` **Automatic creation of non-existing attributes** If the left operand of the condition does not exist, Carbone automatically creates it (`c.new[].id`). ```cdata { "myArray" : [ { "country" : "A" }, { "country" : "A" }, { "country" : "B" } ], "myArray2" : [ { "country" : "C" }, { "country" : "A" } ] } ``` ```ctemplate {d.myArray[]:set(c.new[id=.country])} {d.myArray2[]:set(c.new[id=.country])} {c:printJSON} ``` ```cresult { "new": [ {"country": "A", "id": "A"}, {"country": "B", "id": "B"}, {"country": "C", "id": "C"} ] } ``` ###### Group data in nested arrays Carbone accepts a **search expression** enclosed in square brackets to determine how to add new items. If an item in the new array **matches the condition**, the **new item is merged** with the **existing one** `{ d.myArray[] :set( c.countries[ id = .country].cities[] )}` - `d.myArray[]` : Loops over the existing array and return its content - `:set(c.countries[ id = .country ]`: Injects the content into the new array `c.countries[]` - `c.countries` is the newly created array in `c.` - `[id=.country]` is a search expression used to find existing items that match the criteria - `id` is a relative path to the new object inside `c.countries`. Equivalent to `c.countries[].id` - `.country` is a relative path within the currently visited array. Equivalent to `d.myArray[].country` - `.cities[])`: For each country, injects items into the new nested array `cities[]` ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" }, { "country" : "B", "city" : "2B" }, { "country" : "A", "city" : "3A" } ] } ``` ```ctemplate {d.myArray[]:set(c.countries[id=.country].cities[])} {c:printJSON} ``` ```cresult { "countries": [ { "id": "A", "cities": [ {"country": "A", "city": "1A"}, {"country": "A", "city": "2A"}, {"country": "A", "city": "3A"} ] }, { "id": "B", "cities": [ {"country": "B", "city": "1B"}, {"country": "B", "city": "2B"} ] } ] } ``` **Other examples** Here, only `city` is injected into the `:set`. Carbone creates an array of strings. ```cdata { "myArray" : [ { "country" : "A", "city" : "1A" }, { "country" : "A", "city" : "2A" }, { "country" : "B", "city" : "1B" }, { "country" : "B", "city" : "2B" }, { "country" : "B", "city" : "3B" }, { "country" : "A", "city" : "3A" } ] } ``` ```ctemplate {d.myArray[].city:set(c.countries[title=.country].cities[])} {c:printJSON} ``` ```cresult { "countries": [ { "title": "A", "cities": ["1A", "2A", "3A"] }, { "title": "B", "cities": ["1B", "2B", "3B"] } ] } ``` ##### Join two arrays -> :set(absolutePath\[\]) Here is a basic example to join two separate arrays: ```cdata { "actors": [ { "id" : 10, "firstName" : "Keanu" }, { "id" : 20, "firstName" : "Margot" } ], "movies": [ { "actorId" : 10, "name" : "Matrix" }, { "actorId" : 20, "name" : "Babylon" }, { "actorId" : 10, "name" : "John Wick" } ] } ``` ```ctemplate {d.actors[]:set(c.actors[id=.id])} {d.movies[]:set(c.actors[id=.actorId].movies[])} {c.actors:printJSON} ``` ```cresult [ { "id": 10, "firstName": "Keanu", "movies": [ {"actorId": 10, "name": "Matrix"}, {"actorId": 10, "name": "John Wick"} ] }, { "id": 20, "firstName": "Margot", "movies": [ {"actorId": 20, "name": "Babylon"} ] } ] ``` Get inspired by one of our real-life examples: [Gallery of images](/examples/gallery-simple/index.md), [Bank Statement](/examples/bank-statement-expert/index.md) or [Planning with subtotals](/examples/planification-medium/index.md) --- #### [Pagination](https://carbone.io/documentation/design/advanced-features/pagination.md) How to set page numbers, table of contents, bookmarks, and repeat table header COMMUNITY FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✓ Embedded Carbone JS v2.0+ This documentation highlights a non-exhaustive list of features natively supported by editors (e.g., Microsoft Office, LibreOffice) and automatically updated by Carbone during document generation. ##### Page numbers Insert page numbers with your design tool, and Carbone will update the page number in the generated report. | Tools | In Header, Footer | Elsewhere | | --- | --- | --- | | Microsoft Word | Insert -> Page Numbers | Insert -> Field -> Numbering -> Page Insert -> Field -> Document Information -> NumPage | | LibreOffice | | Insert -> Field -> Page Number Insert -> Field -> Page Count | ##### Table of content Insert a Table of Contents with your design tool, and Carbone will update it dynamically in the generated report. | Tools | Method | | --- | --- | | Microsoft Word | Insert -> Index and Table -> Table of Contents | | LibreOffice | Insert -> Table of Contents and Index -> Table of Contents, Index, or Bibliography | ##### Table header on page break Ensure your table headers repeat automatically at the top of each page when a table spans multiple pages. | Tools | Method | | --- | --- | | Microsoft Word | Right-click on the table header -> Table Properties -> Check "Repeat as header row at the top of each page" | | LibreOffice | Right-click on the table header -> Table Properties -> Text Flow tab -> Check "Repeat heading" | --- #### [Pictures](https://carbone.io/documentation/design/advanced-features/pictures.md) How to integrate dynamic pictures in your template? ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v3.0+ ##### How to insert picture? Compatible with PDF, ODT, ODS, ODP, ODG, PPTX, XLSX, and DOCX files. Dynamic pictures can be a [public URL](#simple-image-odt) or a [Base64 Data URI](#base64-image-odt). For the 2 solutions, a temporary picture has to be inserted in the template and **the tag has to be written as an alternative text**. Finally, during rendering, Carbone replaces the temporary picture by the new picture provided by the tag. The image is resized as 'fillWidth' by default, learn more about the [imageFit formatter](#how-to-insert-picture-image-aspect-ratio-imagefit) to change the value. If an error occurs for some reasons (fetch failed, image type not supported), a replacement error image is used. The place to insert the tag on the temporary picture may change depends on the file format: - `ODS`, `ODP` and `ODG` files: set the tag on the image title - `ODT` file: set the tag on the image alternative text, image description - `DOCX` and `XLSX` files: set the tag either on the image title, image description, or alternative text The accepted images types are `jpeg`, `png`, `gif`, `svg`. **Limitation:** When images are positioned absolutely in a document, creating loops with multiple images produces an invalid report in `PPTX`, `XLSX`, `ODP`, and `ODG` files. A new solution using the [transform](transform.md) formatter is coming soon. Please contact us for more information. However, it is still possible to create a loop across two or more slides in `ODP` files. ###### Image aspect-ratio `:imageFit` The `imageFit` formatter determines how the image should be resized to fit its container. An argument must be passed to the formatter: - `fillWidth`: The replaced image is resized to fill the element’s content-box width, without changing its aspect ratio. - `contain`: The replaced image is scaled to maintain its aspect ratio while fitting within the element’s content-box (the temporary image). - `fill`: The replaced image is resized to fill the element’s content-box (the temporary image). The entire image will fill the box of the previous image. If the image's aspect ratio does not match the aspect ratio of its box, it will be stretched to fit. ```js {d.image:imageFit(contain)} // or {d.image:imageFit(fill)} // or {d.image} // 'fillWidth' is set by default ``` ###### Image auto-orientation `:autoOrient` Available since v5.6.0. Compatible with DOCX templates only. The `:autoOrient` formatter automatically rotates JPEG images based on the EXIF orientation metadata embedded by cameras and smartphones. This ensures images captured on mobile devices appear in the intended orientation, without manual rotation. ```js {d.image:autoOrient:imageFit(contain)} // or without imageFit (defaults to fillWidth) {d.image:autoOrient} ``` **Notes:** - Only JPEG images are rotated. Other image formats (PNG, GIF, SVG) ignore this formatter and render normally. - `:autoOrient` cannot be combined with `imageFit(fill)`. Allowed parameters: `imageFit(contain)` or `imageFit(fillWidth)` instead. ##### Simple image \[ODT\] **Data** ```json { "dog": "http://link.to/the/picture" } ``` **Template** - Insert a [temporary picture](/img/doc/image-placeholder.png) into the template. - Write the tag `{d.dog}` in the picture's alternative text. In LibreOffice, right-click on the image, go to Properties, and then enter the tag in the Alternative Text field. ![Result with Carbone](/img/doc/image-odt-simple.webp) Carbone will replace the temporary picture with the one linked in the "dog" data. Finally, after Carbone renders the report, the new image from the URL should appear! ##### Loop of images \[ODT\] It is also possible to display a list of pictures by writing a loop. Here is the template used in the following example: **Data** ```json { "flags": [ { "name": "France", "picture": "http://link.to/the/flag-fr" }, { "name": "Germany", "picture": "http://link.to/the/flag-de" }, { "name": "Italy", "picture": "http://link.to/the/flag-it" } ] } ``` **Template** - Insert the repetition tag in the description of the first picture. It is not necessary to repeat the placeholder image for the `[i+1]` tag. Carbone will automatically repeat the image. - Update the anchor type of an image: Right-click on the image > Properties > Type > Anchor and select "As Character". | Anchor Type | Carbone Behavior | | --- | --- | | As Character | This is the best choice: it allows for the replacement of a single image or the creation of a loop of images. | | To Page / To Paragraph / To Character | The image is floating, making it impossible to create a loop of images; only a single image replacement is supported. | ![List of images with carbone template](/img/doc/image-loop-template.webp) **Result** Finally, after the Carbone rendering, the list of images appears on the report! ![List of images result](/img/doc/image-loop-result.webp) ##### Base64 image \[ODT\] Before continuing, check out this documentation to learn more about [Data URIs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). **Data** ```json { "frenchFlagImage": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQABLAEsAAD/4QGERXhpZgAATU0AKgAAAAgACQEPAAIAAAAGAAAAegEQAAIAAAANAAAAgAESAAMAAAABAAEAAAEaAAUAAAABAAAAjgEbAAUAAAABAAAAlgEoAAMAAAABAAIAAAExAAIAAAAUAAAAngEyAAIAAAAUAAAAsodpAAQAAAABAAAAxgAAAABDYW5vbgBDYW5vbiBFT1MgNkQAAAAAASwAAAABAAABLAAAAAFBZG9iZSBQaG90b3Nob3AgNy4wADIwMTc6MTE6MjAgMTE6MzE6MTQAAAmCmgAFAAAAAQAAATiCnQAFAAAAAQAAAUCIJwADAAAAAgDIAACQAwACAAAAFAAAAUiSCgAFAAAAAQAAAVygAQADAAAAAQABAACgAgAEAAAAAQAAAGSgAwAEAAAAAQAAAD6kNAACAAAAFwAAAWQAAAAAAAAAAQAAAFAAAAAEAAAAATIwMTY6MDM6MjYgMTM6MDg6MDcAAAAAaQAAAAFFRjI0LTEwNW1tIGYvNEwgSVMgVVNNAAD/4Qn2aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiBwaG90b3Nob3A6RGF0ZUNyZWF0ZWQ9IjIwMTYtMDMtMjZUMTM6MDg6MDciIHhtcDpNb2RpZnlEYXRlPSIyMDE3LTExLTIwVDExOjMxOjE0IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCA3LjAiLz4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+AP/tAGBQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAJxwBWgADGyVHHAIAAAIAAhwCPAAGMTMwODA3HAI3AAgyMDE2MDMyNgA4QklNBCUAAAAAABDW6DtHA0U5WqoLeQsJGPlt/8AAEQgAPgBkAwERAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAgICAgICAgICAwMDAwMDAwMDA//bAEMBAQEBAQEBAQEBAQICAQICAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA//dAAQADf/aAAwDAQACEQMRAD8A/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//Q/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//R/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//S/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//T/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//U/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//V/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//W/nfr/twP8vwoAKALFp/x9W3/AF8Q/wDoxazrfwqv+F/ka0P49D/r5H/0pH+lR/wQt/5RQfsbf9iD4h/9WH4xr/lf/aEf8pjeOP8A2MaP/qFhj/QXw5/5IvIv+vcv/Tkz9aK/jM+2CgAoA/lN/wCDiL/kr37MX/ZJ/i9/6nfwsr+KPpdfBw//ANeJ/wDp6kf72fsef+TfeOP/AGPsp/8AUHMz+dGv4WP9jAoAKACgD//Z" } ``` **Template** - Insert a temporary picture on the template. - Write the tag `{d.frenchFlagImage}` in the title picture property. To access to the title on Libre Office: Right-click on the image > Description > Title. ![Picture from a base64 data URI image](/img/doc/image-ods-template-simple.webp) After the Carbone rendering, the new image from the data URI should appear on the report! ##### Simple image \[DOCX\] **Data** ```json { "image": "http://link.to/you/picture" } ``` Or with base64 URI: ```json { "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" } ``` **Template** The first step is to update the alternative text of the image and set the `{d.image}` tag. ![Step 1 Word Dynamic picture](/img/doc/dynamic-picture-word-step1.gif) **Result** Then the rendering can tested in Carbone studio by adding the corresponding value in the JSON data field. ![Step 2 Word Dynamic picture](/img/doc/dynamic-picture-word-step2.gif) ##### Loop of images \[DOCX\] Follow the same instructions as in [Loop of Images in ODT](#loop-of-images-odt). Update the position type of an image in Word: Right-click -> Size and Position -> Text Wrapping, and select "In Line with Text." | Position Type | Carbone Behavior | | --- | --- | | In Line with Text | Best choice: Allows for the replacement of a single image or a loop of images. | | Other Wrapping Styles | The image is floating; it is not possible to create a loop of images, only a single image replacement is accepted. | Get inspired by one of our real-life examples: [Certificate](/examples/certificat/index.md), [Property advertisement](/examples/real-estate-property-description/index.md), [Quote With Datasheet](/examples/quote-with-datasheet/index.md), [Gallery of images](/examples/gallery-simple/index.md) or [Vehicle Inspection Report](/examples/check-out/index.md) --- #### [Colors](https://carbone.io/documentation/design/advanced-features/colors.md) Applying dynamic colors to elements (text, row, cell, ...) ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v4.0+ ##### :color(scope, type) v4.17.0+ Apply the color on the text, paragraph, table row or table cell | Params | Description | Type | | --- | --- | --- | | scope | Specifies the element to be color-changed \- `p` : the current paragraph where the tag is (by default) \- `cell` : the current cell of a table \- `row` : the current row of a table \- `shape` : the current shape \- `part` : for text section (the color is applied within the same styled of the element) | String | | type | Defines the property to be modified. \- `text` for the text color (by default) \- `highlight` to highlight the text \- `background` for cells, rows and shapes \- `border` for shapes only | String | **Important Notes:** Compatible with PDF, ODT, ODP, DOCX, PPTX, HTML files. - The color provided must be in 6-digit hex color notation, with or without a hashtag, and can be in lowercase or uppercase: `#FF0000` or `FF0000`. Carbone replaces incorrect color values with light gray (`#888888`). - The `:color` formatter manages priority between overlapping areas. A color applied to the current paragraph has a higher priority than a color applied to the current shape, cell, or row. - For HTML, the color is applied using the `style` attribute only to the selected HTML tag (`tr`, `td`, `p`). - For ODP (text, tables, and shapes) and ODT (shapes only), the `:color` formatter requires that a non-default style is already applied to the target element in the template (e.g., a custom text color). - For PPTX, all combinations of `type` (`background`, `text`, `border`) and `scope` (`row`, `cell`, `shape`) are supported, except `border` which applies only to shapes. Available since v5.6.0. - Complex nested tables with colors on sub-tables are not fully supported. - `highlight` is not managed in DOCX templates. - The `:color` formatter cannot be used in aliases and cannot be combined with aggregators. - The `:color(part)` feature is currently available only for ODT templates. Get inspired by one of our real-life examples: [Invoice](/examples/invoice-simple/index.md) or [Sensor Readings](/examples/sensor-readings/index.md) ##### Simple text color The following example changes the text color to red: ```cdata { "flowerColor": "#FF0000" } ``` ```ctemplate Color of roses {d.flowerColor:color(p)} This paragraph is not colored. ``` ```cresult Color of roses This paragraph is not colored. ``` ##### Condition and color The following example changes the text color based on a condition. Details: - If the `test` value is equal to "OK," the text color will be `d.success` (color "#007700"). - If the `test` value is not equal to "OK," the text color will be `d.error` (color "#FF0000"). - The `:color(p)` formatter applies the color to the text in the current paragraph. ```cdata { "test" : "OK", "testError" : "ERROR", "success" : "#007700", "error" : "#FF0000" } ``` ```ctemplate The assessment passed {d.test:ifEQ(OK):show(.success):elseShow(.error):color(p)} Error color {d.testError:ifEQ(OK):show(.success):elseShow(.error):color(p)} ``` ```cresult The assessment passed Error color ``` ##### Loop and color The example conditionally changes the text color of the current table row. In the following example, the Carbone tag `{d.tests[i].result:ifEQ(ok):show(#000000):elseShow(d.error):color(row, text)}` is used in the template: - If the `result` value is equal to "ok," the text color will change to black (`#000000`). The argument passed is a string, not a value sourced from the JSON. - If the `result` value is not equal to "ok," the color will be `d.error`. - The `:color(row, text)` formatter changes the text color in the current row. ```cdata { "error": "#FF0000", "tests": [ { "name": "Security Training","result": "ok" }, { "name": "Code Auditing","result": "20 Vulnerabilities found" }, { "name": "Firewall Testing","result": "ok" } ] } ``` **Template:** | Test | info | | --- | --- | | {d.tests[i].name} | {d.tests[i].result} {d.tests[i].result:ifEQ(ok):show(#007700):elseShow(d.error):color(row, text)} | | {d.tests[i+1]} | | **Result:** | Test | info | | --- | --- | | Security Training | ok | | Code Auditing | 20 Vulnerabilities found | | Firewall Testing | ok | ##### Combined colors The example conditionally changes the background color and text color of the table row. Two tags are used in the following template: - `{d.tests[i].result:ifEQ(ok):show(#000000):elseShow(#FFFFFF):color(row, text)}`: If `result` is equal to `ok`, the text color of the current row is changed to black (`#000000`); otherwise, it changes to white (`#FFFFFF`). - `{d.tests[i].result:ifEQ(ok):show(#FFFFFF):elseShow(d.error):color(row, background)}`: If `result` is equal to `ok`, the row keeps a white background (`#FFFFFF`); otherwise, it takes the red color sourced from the JSON (`d.error`). ```cdata { "error": "#ffd9d9", "tests": [ { "name": "Security Training","result": "ok" }, { "name": "Code Auditing","result": "20 Vulnerabilities found" }, { "name": "Firewall Testing","result": "ok" } ] } ``` **Template:** | Test | id | | --- | --- | | {d.tests[i].name} | {d.tests[i].result} {d.tests[i].result:ifEQ(ok):show(#000000):elseShow(#d90000):color(row, text)} {d.tests[i].result:ifEQ(ok):show(#FFFFFF):elseShow(d.error):color(row, background)} | | {d.tests[i+1]} | | **Result:** | Test | id | | --- | --- | | Security Training | ok | | Code Auditing | 20 Vulnerabilities found | | Firewall Testing | ok | ##### Color Text Section Compatible with PDF, and ODT files for the moment. The `:color` formatter includes a **part** argument, which allows you to apply color to a specific section within a paragraph. This feature is particularly useful when you need to highlight certain parts of your text without affecting the entire paragraph. To correctly use the `:color(part)` formatter, follow these steps: 1. **Tag Placement**: Place the tag with the `:color(part)` formatter right next to the text you want to color. 2. **Selecting Tags**: In LibreOffice or MS Word, select the tags you want to color, including the tag containing the `:color(part)` formatter. 3. **Clear Style** : Click on the "Clear Direct Formatting" icon located in the "Home" tab. This will define the section to be colored for the `:color(part)` formatter. 4. **Reapply Font Style**: If you had a specific font style, reapply it after clearing the direct formatting. ##### Color replacement {bindColor} We recommend using [`:color`](#color-scope-type) formatter instead of `{bindColor}` because it is much simpler to use and less error-prone. `{bindColor}` was the old method in Carbone for managing color. Compatible with PDF, ODT, ODP, DOCX, and ODS files. The special `bindColor` tag allows you to replace a color reference in the template with a new color from your JSON dataset. Usage: `{bindColor(myColorToBind, myFormat) = d.myVar}` - `myColorToBind`: A temporary hexadecimal color applied to text, background, or a cell background. It is used by Carbone to identify the color to replace. The hexadecimal value is case-insensitive, and the hashtag at the beginning is optional. Note an exception regarding [dynamic background colors on MS Word DOCX documents](#color-replacement-bindcolor-microsoft-word-exception-for-bindcolor). - `myFormat`: The format of the new color expected from the `d.myVar` tag. Here is a list of available [color formats](#color-replacement-bindcolor-color-formats-for-bindcolor). - `d.myVar`: The tag that corresponds to the new color. Unlike regular tags, `{bindColor()}` can be placed anywhere in the document, such as the header, footer, or below a repetition `[i+1]`, even if the `myVar` argument is part of a repetition. For more details, refer to [the example below](#color-replacement-bindcolor-simple-example-with-bindcolor). **Important Notes:** Supported on: - Text and background colors - Table cell backgrounds - Shape backgrounds and line colors for **DOCX only**. Write the `bindColor` tag in the document body, not in the alternative text of shapes. The replaced color format must be `RGB` only. ###### Simple example with {bindColor} We recommend using [`:color`](#color-scope-type) formatter instead **Data** ```json { "color": "#FF0000", // red "color2": "#00FF00", // green "color3": "#0000FF" // blue } ``` **Template** ![Basic dynamic color](/img/doc/dynamic-color-1.png) **Details:** - `Manage` get the `#00FFFF` reference color and will be replaced by `#FF0000` coming from the `{d.color}` tag. - `dynamic` get the `#FF00FF` reference color and will be replaced by `#00FF00` coming from the `{d.color2}` tag. - `color` get in background the reference color `#0000FF` and will be replaced by `#00FF00` coming from the `{d.color3}` tag. **Result** ![Basic dynamic color result](/img/doc/dynamic-color-1-result.png) ###### Color formats for {bindColor} We recommend using [`:color`](#color-scope-type) formatter instead There are 5 color formats: - `#hexa`: ```json { "color": "#FF0000" } ``` - `hexa`: ```json { "color": "FF0000" } ``` - `color`: ```json { "color": "red" } ``` - `rgb`: ```json { "color": { "r": 255, // Between 0 and 255 "g": 0, // Between 0 and 255 "b": 0 // Between 0 and 255 } } ``` - `hsl`: ```json { "color": { "h": 300, // Between 0 and 360 "s": 50, // Between 0 and 100 or 0 and 1 "l": 50 // Between 0 and 100 or 0 and 1 } } ``` ###### Microsoft Word exception for {bindColor} We recommend using [`:color`](#color-scope-type) formatter instead In Microsoft Word, only a **color name** can be used to replace the **background** color of a text. 17 colors are available, which are: ```text yellow | green | cyan | magenta | blue | red darkBlue | darkCyan | darkGreen | darkMagenta | darkRed | darkYellow darkGray | lightGray | black | white | transparent ``` You can still use hexadecimal colors for the **text** and **cells backgrounds** on Microsoft Word. **Data** ```json { "color": "darkYellow", "color2": "green", "color3": "11BBCC" } ``` **Template** `Microsoft` get in background the `red` color, `Word` get in background the `magenta` color and `background` get the `#FFFF00` color ![Word color exception](/img/doc/dynamic-color-2.png) **Result** ![Word color exception result](/img/doc/dynamic-color-2-result.png) ###### Loop with {bindColor} We recommend using [`:color`](#color-scope-type) formatter instead Each line in a table can be colorized by one color: **Data** ```json { "user": [ { "firstname": "Jean", "lastname": "Dujardin", "color": { "r": 255, "g": 0, "b": 0 } }, { "firstname": "Omar", "lastname": "Sy", "color": { "r": 0, "g": 255, "b": 0 } } ] } ``` **Template** The first line get the `#0000FF` color and the second line get the `#00FFFF` color. Each line get the color corresponding to the color key in each user object. ![Table color](/img/doc/dynamic-color-table.png) **Result** ![Table color result](/img/doc/dynamic-color-table-result.png) --- #### [HTML](https://carbone.io/documentation/design/advanced-features/html.md) HTML Rendering for Documents: Transforming WYSIWYG and AI-Generated HTML into ODT, DOCX, and PDF. ENTERPRISE FEATURE Available for: ✓ Carbone Cloud ✓ Carbone On-premise ✗ Embedded Carbone JS v5.0+ ##### :html Compatible with ODT, DOCX, HTML and PDF. The `:html` formatter converts HTML content into native document formatting for DOCX and ODT templates, enabling rich text, structured layouts, and semantic document elements. List of Supported HTML Elements: | Category | Elements | Documentation | | --- | --- | --- | | **Text formatting** | ``, ``, ``, ``, ``, ``, `` | / | | **Structure** | `

`, `
` | / | | **Lists** | `