bindColor
in PPTX templates (for text, background color, and shapes). This feature is enabled when the flag {o.preReleaseFeatureIn=4022011}
is included in the template.en-be
locale support for dates and numbers: DD/MM/YYYY
, 100.000,80
&
, >
, and <
for the aggregators :aggStr
and :aggStrD
.:set()
formatter when {o.preReleaseFeatureIn=4022011}
is activated:t()
formatter to translate text from JSON data.
When the report is rendered, all text passed to the :t
formatter is replaced with its corresponding translation found in a separate localization dictionary.
Carbone automatically selects the localization dictionary that matches the lang
attribute (en-us
, en-gb
, etc.).
If a translation key is not found, Carbone prints the original text from the data in the final report (without translation).:attachFile
formatter accepts URL with text/xml
mimetype:transform
formatter in ODT/ODP when LibreOffice uses a different method to update the X/Y position of an item.:transform
formatter support to move images in ODT/ODP/PPTX (only if {o.preReleaseFeatureIn=4022011}
is activated){o.preReleaseFeatureIn=4022011}
and {o.useHighPrecisionArithmetic=true}
are removed from the generated document.{o.preReleaseFeatureIn=4022011}
is now detected before processing :set
formatters.:drop(shape)
regression in PPTX templates when using native charts.Release August 30th 2024
New formatter :printJSON
to print the content of an object/array. Example: {d:printJSON}
prints the entire data object in the document.
[EE] When using asynchronous jobs with carbone-webhook-url
, optional headers carbone-webhook-header-X
can be specified to customize the webhook headers sent. The X
can be any key name.
For example, if carbone-webhook-header-authorization: my-secret
is included during report generation, the header authorization: my-secret
will be added when Carbone calls the webhook.
[EE] New formatter {d.pdf_or_xml_url:attachFile(filename)}
to add one or multiple attachments to the generated PDF (e.g., Factur-X).
This formatter accepts a single URL, can be positioned anywhere within a template, and does not produce any text output by itself.
Carbone will return an error and will not generate the report in the following scenarios:
maxDownloadFileCount
).maxDownloadFileSizeTotal
).Carbone will ignore the formatter and proceed to generate the report in the following cases:
[EE] Fix: The :color
formatter did not apply the style correctly in HTML.
ODS template supports :drop(sheet)
and :keep(sheet)
to show or hide a sheet
XLSX template supports :drop(row)
and :keep(row)
Accept whitespace in attribute names using single quotes: {d.'new param'.'second level yes'[1].'sub obj'}
if {o.preReleaseFeatureIn=4022011}
is added in the template.
Known limitation: not accepted inside formatters, array filters and aliases.
Fix: Conditional blocks (show, hide) and smart conditional blocks (drop, keep) were not executed when combined with an array filter.
In this example {d.arr[i, filter=3].id:ifEM:hideBegin} AAAA {d.arr[i, filter=3].id:hideEnd}
, AAAA
was printed if no rows matched the condition filter=3
.
This fix is available only when the tag {o.preReleaseFeatureIn=4022011}
is activated.
⚡️ Transform your JSON without limits with :set()
. This formatter, introduced in v4.5.2, was previously limited.
Now it can be used to convert a flat JSON to a new hierarchical JSON with unlimited array depth, or the other way around.
This feature is activated by adding the tag {o.preReleaseFeatureIn=4022011}
and will be enabled by default in the upcoming Carbone v5.
:set(.attribute.id)
: Accepts a relative path to modify or add new attributes/objects inside an existing object.
:set(c.new[x=.y])
: Creates or updates new arrays of objects.
c.new
is the newly created array in c.
. It can then be used with {c.new}
as usual.[x=.y]
is an inner join expression. Only the =
operator is accepted, and only exactly one expression is allowed between []
inside :set
.x
is a relative path to the newly created object inside c.new
. This expression cannot contain dots or square brackets..y
is a relative path in the currently visited array (the tag before the :set
expression). Use two or more dots to go up in the hierarchy if needed.How does it work? Before creating a new object inside c.new
, Carbone checks if there is an existing object that matches
the condition new.x = currentlyVisitedItem.y
. If an object exists, it overwrites the existing object;
otherwise, it inserts the new object inside the new array.
This join expression works only on newly created arrays by Carbone, not on existing ones in the original JSON data passed when calling Carbone.
:set(c.new[x=.y].z)
: Creates or updates new arrays of objects and stores the result of the expression before :set
in the child attribute z
.
:set(c.new[x=.y].other[n=..m].o)
: Creates any nested array inside arrays with multiple join expressions.. Here Carbone creates two nested arrays new
and other
.
:set(c.new[x=.y].all[])
: An empty join expression is accepted only for the last array in :set
.
In that case, all items are kept (including duplicated rows).
Data:
```json
[
{ "country" : "A", "city" : "1A" },
{ "country" : "A", "city" : "2A" },
{ "country" : "A", "city" : "3A" },
{ "country" : "B", "city" : "1B" },
{ "country" : "B", "city" : "2B" }
]
```
Result in `{c.}` with `{d[].city:set(c.countries[id=.country].cities[].name)}`:
```json
{
"countries" : [
{
"id" : "A",
"cities" : [
{ "name" : "1A" },
{ "name" : "2A" },
{ "name" : "3A" }
]
},
{
"id" : "B" ,
"cities" : [
{ "name" : "1B" },
{ "name" : "2B" }
]
}
]
}
```
Result in `{c.}` with `{d[]:set(c.countries[title=.country].cities[])}`:
```json
{
"countries" : [
{
"title" : "A",
"cities" : [
{ "country" : "A", "city" : "1A" },
{ "country" : "A", "city" : "2A" },
{ "country" : "A", "city" : "3A" }
]
},
{
"title" : "B" ,
"cities" : [
{ "country" : "B", "city" : "1B" },
{ "country" : "B", "city" : "2B" }
]
}
]
}
```
Result in `{c.}` with `{d[].city:set(c.countries[title=.country].cities[])}`:
```json
{
"countries" : [
{
"title" : "A",
"cities" : [ "1A", "2A", "3A" ]
},
{
"title" : "B" ,
"cities" : [ "1B", "2B" ]
}
]
}
```
config.factories = 0
(starting without the LibreOffice converter), the hardRefresh
option is ignored.:transform(axis, unit)
for PPTX template did not work in certain casesRelease July 17th 2024
[EE] The embedded stateless studio supports XML templates if the following conditions are met: Chromium-based browsers, output file type must be XML. The generated XML is indented and stored locally. It supports live reloads. If the template/data is updated, the generated XML is updated accordingly. VSCode, Sublime Text, or any editing tool can be used to update the XML template and see the result.
[EE] Fixed :imageFit
formatter for resizing image in ODS templates.
[EE] Fix: Do not escape the character &
two times in injected hyperlinks if {o.preReleaseFeatureIn=4022011}
is active
[EE] New :transform(axis, unit)
formatter to move a shape on the X or Y axis:
axis
can be x
or y
to move the item horizontally or vertically from its current position. Positive numbers = move to the right or bottom.unit
can be cm
, mm
, inch
, or pt
.The tag must be positioned inside the item to be transformed (alternative text or inside the shape itself).
This tag prints nothing. Supported in ODP, PPTX and ODT templates.
The feature is available only if the tag {o.preReleaseFeatureIn=4022011}
is used.
Be careful, if a loop is created between two shapes, the [i+1]
tag must be in the shape that is higher in the layer order than the shape with the [i]
tag.
:drop
and :keep
formatters to control the visibility of elements:p
: insert the tag in a paragraph to show/hide: Text before {d.value:ifEM:drop(p)}
. To delete multiple paragraphs simultaneously, specify the number of elements as a second argument: :drop(p, number)
.img
: use the tag in an image's alternative text or title to manage visibility: {d.url:ifEM:drop(img)}
.shape
: place the tag in a text box or shape's alternative text to show or hide the shape: {d.showShape:ifEQ(false):drop(shape)}
.table
: insert the tag in a table cell to control the display of the table: {d.list:ifEM:drop(table)}
.row
: embed the tag in a table row to manage its visibility: {d.value:ifNE(20):drop(row)}
. To delete multiple rows simultaneously, specify the number of elements as a second argument: :drop(row, number)
.chart
: use the tag in the chart's alternative text to show or hide the chart: {d.list:ifEM:drop(chart)}
.:html
formatter: duplicating Bold or Italic tags is now supported{o.preReleaseFeatureIn=4022008}
is used i+1
part of a loop for backward compatibility.:color(part)
accepts the new option part
to color only a part of a paragraph (word, group of words, ...). The text or highlight color is applied within the same styled element.
This feature is available only for ODT templates. Other template types will be supported on demand.:ceil(number)
rounds towards closest higher integer number 3.5 -> 4
and -3.5 -> -3
:floor(number)
rounds towards closest lower integer number 3.5 -> 3
and -3.5 -> -4
{d:showEnd}
or {d:hideEnd}
is sufficient. {d.arr[sort>1].id:aggCount:ifGT(0):showBegin}
{d.arr[i].id}
{d.arr[i+1].id}
{d:showEnd}
⚠️ This algorithm is enabled when the template contains the tag {o.preReleaseFeatureIn=4022008}
or if the configuration / API contains preReleaseFeatureIn : 4022008
.
This tag means: "enable all pre-release features added up to v4.22.8".{o.preReleaseFeatureIn=4022008}
is activated, Carbone improves the support of XLSX templates (no need to add empty cell)showBegin/showEnd
or hideBegin/hideEnd
conditions in some rare cases when {o.preReleaseFeatureIn=4015000}
is enabled.:html
evolution of v4.22.6
(v4.22.6
was not deployed in production):HTML
formatter: the HTML is now supported in headers and footers of DOCX/ODT/PDF. Temporary exception for DOCX templates: if the HTML contains <img>
tags, a image must be pre-inserted into the header/footer of the template, otherwise the image won't be displayed.:html
formatter are also applied to the injected HTML for DOCX/ODT templates.
This fix addresses numerous spacing issues between paragraphs, before or after the injected HTML. List of new supported styles:{
myObject : {
paul : '10',
jack : '20',
bob : '30'
}
}
In the report: {d.myObject[.att = jack].val} // 20
{d.myObject[.val = 20].att} // jack
{o.useHighPrecisionArithmetic=true}
can be used within a template to activate arbitrary-precision decimal arithmetic for mathematical operations.
When this tag is included in the template, the formatters :add
, :sub
, :mul
, :div
, :formatC
, :formatN
will maintain full decimal precision (Max 20).
By default, and for all other formatters (such as aggregators, :abs
, :mod
, as well as mathematical expressions inside formatters and conditions), calculations uses
double-precision floating-point numbers based on the 64-bit IEEE-754 standard.drop(table)
for ODP templatesdrop(item)
for ODP and ODT templates to delete an item inside a listnull
values are passed in Apache Echarts options.echarts@v5a
instead of echarts@v5
when configuring your JSON.
This @v5a
version will become the default in Carbone v5.{
"chartOptions" : {
"type" : "echarts@v5a",
"option" : {}
}
}
aggSum:formatN
is used since 4.22.0Release April 5th 2024
The formatter convCRLF
supports PPTX templates
[EE] new formatter {d.pdf_url:appendFile}
to append one or several PDFs at the end of the generated document.
This formatter accepts either a single URL. It can be positioned anywhere within a template and does not produce any text output by itself.
Carbone will return an error and will not generate the report in the following scenarios:
maxDownloadFileCount
).maxDownloadFileSizeTotal
).Carbone will ignore the formatter and proceed to generate the report in the following cases:
Improve file type detection when inserting images or PDF coming from external URL. It can read file extension in Content-Disposition
if Carbone cannot find it Content-Type
and URL.
The execution order of multiple Carbone tags using the :set
formatter is guaranteed within the same part of a document (body, header, footer, text box).
As a result, you can create new variables that depend on previously created ones.
When an existing set expression is used to create another set expression, the new expression must be define after or bellow the existing expression.
Example:
{d.price:add(10):set(d.total1)}
{d.total1:add(20):set(d.total2)}
{d.total2} = 30
Supports variables with absolute JSON paths starting with d.
or c.
in mathematical formatters: add
, sub
, div
, mul
. For example: {d.total:add(d.val)}
.
Fix: Reduced the probability of generating corrupted XLSX files when using XLSX templates with :formatN
to transform a JSON number into a native Excel number.
Previously, if at least one injected value was a string, the generated XLSX file would become corrupted.
[On-premise] Load custom formatters as Javascript in your Carbone instance:
formatters.js
under the plugin
folder.module.exports = { }
, then insert your Javascript function between curly brackets: One function is equal to one formatter.function addText (d, text) {
return d + text;
}
/**
* Details:
* "d" the tag value
* "text" the first argument of the formatter
* A value must be returned otherwise it will print nothing in the document.
* Example usage: {d.value:addText(' euro')}
*/
Find formatters examples on the following page: https://github.com/carboneio/carbone/blob/master/formatters/string.jsAccept to send a volatile template when calling the API POST /render/template
. This template is never stored and it does not trigger the middleware readTemplate
{
data : {},
template : "base64-encoded-file",
convertTo : "pdf"
}
If options.isDebugActive = true
. POST /render
returns internal metrics information in debug
sub-object. All Time
values are in microsecond.
{
debug : {
metrics : {
preProcessTime : 0,
planTime : 0,
mergeTime : 0,
concatTime : 0,
fetchImageTime : 0,
fetchImageBytes : 0,
fetchFileTime : 0,
fetchFileBytes : 0,
postProcessTime : 0,
convertTime : 0,
renderTime : 0,
batchSize : 1
}
}
}
style:use-optimal-row-height='true'
parameter.:color
formatter supports updating the background color of a page with :color(page, background)
options in DOCX templates.:aggCountD
: Count the number of distinct values. Null or undefined values are ignored.:aggStrD
: Aggregate distinct values. Null or undefined values are ignored.:formatC(precision, currency)' formatter accepts an optional second parameter to force the target currency. It overrides the global
currencyTarget` option.currencySource
is undefined, no conversion is done when using the formatters convCurr
and formatC
.fr-CH
and rm-CH
. Thousand separator is '
and decimal separator is .
like de-CH
and it-CH
.{d[i+1*qty]
can be set globally with the maxRepetitionFactor
parameter (400 by default).substr(begin, end, wordMode)
when word mode is active. If wordMode=true
, it never cuts words.
It can be used to print successive lines of text of constant width.
The previous algorithm of v4.12.0 could create some gaps (missing words) when doing successive calls of substr
.
Here is the problem: substr
in the template is calculated independently.substr
must obey these constraints:end
- begin
) must be used between successive calls of substr
to print all the words of the text without gaps. For example:{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 characterslast
can be used instead of true
to print the rest of the text, even if it is longer than the defined line width.carbone.renderXML
accepts html templates if option.extension
is html
drop
in ODS (img, row) and HTML (table, p, row) templates.keep
, the opposite of drop
:color(scope, type)
formatter scope
can be p
(by default), cell
, row
, shape
to apply color to the current paragraph, cell, row or shapetype
can be text
(by default), highlight
for text, background
for cells, rows and shapes, border
for shapes only#FF0000
or FF0000
. Carbone replaces wrong color values with light gray (#888888)bindColor
::drop
or :keep
).{d.col:color(p)} {d.nb:ifEQ(true):show(FF0000):color(p, highlight)}
is possible.:color
formatter requires that a non-default style is already applied to the target element in the template (for example, a custom text color).highlight
is not managed in docx template:color(scope, type)
bug for DOCX templates when the Carbone tag :color
tag is quite long with many conditions for example (v4.16.0):aggStr(separator)
formatter, a more powerful version of :arrayMap
. By default the separator is ', '.d.user.phones[]:aggStr()
-> +331112345678, +331112345678, +331112345678
d.cars[electric=true].brand:aggStr(' - ')
-> Tesla - BYD - Kia
:set(c.value)
accepts to store values in complement object c.
formatOptions
when convertTo
is an object:color(scope, type)
formatter only for DOCX template: {d.myHexColor:color(p)}
p
(by default), cell
, row
for applying color on the current paragraph, cell and row.text
(by default), background
for cells and rows.#FF0000
or FF0000
. Carbone replaces incorrect color values with white (#FFFFFF):cumCountD
to count the number of distinct values:html
formatter did not remove forbidden characters such as
in DOCX, ..., XLSX, ODT {d.list[0].name} {d.list[i].name}
{d.list[i+1].name}
CARBONE_EE_CLEANINTERVALTIME
, or --cleanIntervalTime
CLI options, or set cleanIntervalTime
on the configuration file.
The value must be in minutes (minimum : 1, maximum : 35791). The default value is "every 10 minutes". :html
formatter: the default font size from a DOCX template is always applied on the rendered HTML.GET /
returns the same response as GET /status
when the embedded studio is disabled..
. Example: {d.array[i]..otherArray[condition=other].id}
drop
is used on i+1
part of an array IF
START LOOP
END IF
END LOOP
# END IF should be here for a computer. Carbone fixes it automatically
This v5 engine is only enabled if the template contains the tag {o.preReleaseFeatureIn=4015000}
or if the configuration / API contains preReleaseFeatureIn : 4015000
.
This tag means: "enable all pre-release features added up to v4.15.0".:imageFit
formatter is now supported for PPTX documents.XML Word 2003
can be used to generate: pdf
, txt
, docx
, odt
, doc
, jpeg
, png
XML Excel 2003
can be used to generate: pdf
, csv
, ods
, xlsx
, xls
, jpeg
, png
POST /render/:templateId
: when a body request is larger than the maxDataSize
(Carbone CLoud API limit is 60MB), a 413
code is returned instead of 500
, and the error message is Content too large, the JSON size limit is X MB
instead of PayloadTooLargeError
.:html
formatter for ODT/DOCX/PDF documents: '
will print '
.401
code is returned instead of 500
.POST /template
: if the template
attribute is missing, a 422
code is returned instead of 400
.POST /template
: if the template format is not supported, a 415
code is returned instead of 200
.POST /render/:templateId
: if the data
attribute is missing, a 422
code is returned instead of 400
.GET /render/:renderId
: if the generated document does not exist, a 404
code is returned instead of 200
.DELETE /render/:templateId
: if the template does not exist, a 404
code is returned instead of 400
.ifNIN
when the data is null
or undefined
html
formatter is used and the text contains &D;
:split():arrayJoin('', 2, 3)
substr
null
or undefined
valuesellispis
-> ellipsis
formattermaxTemplateSize
(bytes) to change default limit (20MB)GET /template
: If the template doesn't exist, the statusCode 404
is returned instead of 400
.POST /template
: If the template is too large, the statusCode 413
is returned instead of 400
ellipsis(maxLength)
: add three dots ...
if the text is longer than maxLength
substr(begin, end, wordMode)
: add a third option to cut the text without cutting a word if wordMode = true (false by default)abs()
: get the absolute value of a numberreplace(oldText, newText)
: replace a text by anothersplit(delimiter)
: split a text using a delimiter and generate an arrayprepend(text)
: add a prefixappend(text)
: add a suffixarrayJoin(separator, index, count)
: add two options to cut the array and select only a part of it400 Bad request
instead of 404 Not Found
when idTemplate
is not validapplication/binary
and image extension in capital lettersdrop
formatter in headers and footers of DOCX and ODT documents.:drop(h)
can be used to delete heading elements for ODT templates only. On LibreOffice, it relates to heading style 1, 2, 3, 4 and custom styles.
For DOCX templates, :drop(p)
must be used to delete heading elements.:html
or :drop
with an empty value inside a table cell no longer creates a corrupted document with DOCX templates.drop
formatter was not found and not executed by Carbone<ul>
or <ol>
lists are now correctly rendered into DOCX documents.options.isDebugActive = true
: do not return internal (not visible by user) tagsGET /template/:templateId
and GET /render/:reportId
HEAD /render/:renderId
: The request don't delete the generated document anymore.GET /template/:templateId
: If the template doesn't exist, the statusCode 404
is returned instead of 400
.svg
option to improve print quality: {d.number:barcode(qrcode, svg:true)}
d
is an array for DOCX templatesformatN
converts values to native Excel number even if d
is an array. formatN
without parenthesis is also accepted.Release March 3rd 2023
Accept absolute path which starts by d.
or c.
without quotes in formatters like this : {d.id:print(d.other)}
or {d.id:print(c.other[0].test)}
[EE] Force download of the rendered report when the query parameter ?download=true
is set
[EE] Increase request timeout from 5 to 6 seconds to download images from URLs
Accept loops on array of arrays (unlimited number of nested array) like this:
{d.myArray[i][i].val}
{d.myArray[i][i+1].val}
{d.myArray[i+1].val}
Support aggregators on array of numbers. For example:{d.table[]:aggSum}
= 63 if table
contains [10, 20, 33]
. Known limitation: Array filters on array of strings/numbers are still not supported.
⚡️ Support loops on array of string/numbers. Example:
Data:
{
"myArray" : ["yellow", 125, "blue"],
}
Template:
Direct access: {d.myArray[0]:upperCase()}
Loop:
- {d.myArray[i]}
- {d.myArray[i+1]}
Result:
Direct access: YELLOW
Loop:
- yellow
- 125
- blue
To maintain backward compatibility with existing templates, if the template contains at least one attribute access
on myArray
(Example: {d.myArray[i].subObjectAttribue}
), Carbone considers myArray
is an array of objects
and it disables this feature. In this case, {d.myArray[i]}
prints nothing and is neutral for array filters even if
the array contains a printable type like a string or a number, as it was before v4.9.0.
Data:
{
"otherArray" : [
{"id" : 10},
{"id" : 20},
125,
"blue"
]
}
Template:
Without filters:
- {d.myArray[i]} {d.myArray[i].id}
- {d.myArray[i+1]}
With filters:
- {d.myArray[i]} {d.myArray[i, id>9].id}
- {d.myArray[i+1]}
Result:
Without filters:
- 10
- 20
-
-
With filters:
- 10
- 20
Known limitation: Array filters on array of strings/numbers are still not supported.
[EE] Includes a part of the new v5 engine that supports more use cases in some complex templates. This v5 engine is only enabled
if the template contains the tag {o.preReleaseFeatureIn=4009000}
. This tag means: "enable all pre-release features added up to v4.9.0".
Please only use this tag if we tell you to use it on our live chat.
{d[i+1*qty]
getPublicKey
must take two arguments (err, publicKeys)
.
If the first argument contains an error, the bad token is kept in quarantine area for 60s instead of infinitely.hardRefresh : true
if the output file type is the same as the template type, and the file type is unknown by LibreOffice (example: XML to XML){d[type=ok].id} {d[type!=ok].id}
:drop(row, nbrToDrop)
for DOCX documents: bookmarked table rows can be deleted.i
{d.arr[i, i<-2].id} {d.arr[i+1, i<-2].id}
print all elements except the last 2 items{d.arr[i, i<-1].id} {d.arr[i+1, i<-1].id}
print all elements except the last itemtable
for the :drop(element)
formatter to delete table conditionally. Available for DOCX, ODT and ODP templates. Usage: {d.value:ifEM:drop(table)}
. The tag must be located within a table cell.:barcode(type)
, such as creating a QR Code: {d.productCode:barcode(qrcode)}
.Carbone
when webhook is called. Print HTTP status webhook response in logs.ifTE(type)
formatter: boolean
, binary
, array
, object
, number
, integer
. Usage: {d.value:ifTE(number):show('It is a number!')}
, the conditional formatter checks if the type of the value is a number.:set(d.value)
. Known limits:d
, and only in d
.aggSum
, aggAvg
, aggMin
, aggMax
, aggCount
when used with filters (without iterators) like {d.cars[id=1].wheels[size=100].makers[].qty:aggSum}
mod()
to compute modulo{d.val:mod(2)}
returns 0 if d.val
= 4:drop(element)
formatter:slide
is available for ODP template only to delete slides. Usage: {d.value:ifEM:drop(slide)}
.drop(p, nbrParagraphs)
accepts a second parameter nbrParagraphs
to delete multiple paragraphs at once. For instance {d.data:ifEM:drop(p, 3)}
, meaning
the current and next two paragraph will be removed if the condition is validated. By default, the formatter :drop(p)
hides only the current paragraph.aggSum
, aggAvg
, ...) convert string to floats. null
or undefined
values are converted to.
accepts dynamic array access between brackets [.i]
:
It can be used to select the corresponding element of another array: {d.myArray[i]:myFormatter(..otherParentArray[.i].id)}
.
[.i]
matches with the index of myArray[i]
. Multiple dots ([..i]
, [...i]
) can be used to access other parents arrays.
If the index goes above otherParentArray
length, it will return an empty string:html
formatterxlsmEnabled
to accept export to xlsm
format (false by default)ifTE(string)
to test if a value is a string. Only "string" is supported for now.d.fromDate:diffD(toDate, unit, patternFrom, patternTo)
.fromDate
and toDate
can be ISO 8601 format or any format defined with patternFrom
and patternTo
unit
can be 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
, quarter(s)
or Q
.
Here are examples with d.fromDate = 20101001
:{d.fromDate:diffD(20101201, days)}
=> 61
{d.fromDate:diffD(20101201, hours)}
=> 1465
formatD
ignores the timezone if only a date is parsed without the time (without hour, minute, second).
Example: when the timezone is america/guayaquil
in options.timezone
'2010-12-01':formatD(LL)
returns December 1, 2010
. Before this version Carbone returned November 30, 2010
'2010-12-01T01:00:00Z':formatD(LL)
returns November 30, 2010 8:00 PM
hide
formatter becomes drop
to avoid confusion with hideBegin/hideEnd/show
. hide
was introduced in 4.2 and is still not offically documented.drop(row, nbrRows)
accepts a second parameter to select the number of row to removedrop
formatter can't be used into Carbone aliases.{d.text:print(''):print('HIGK LMN')}
prints HIGK LMN
instead of HIGKLMN
[...]
{
parent : {
qty : 5,
arr : [{
id : 1
}]
},
subArray : [{
text : 1000,
sub : {
b : {
c : 1
}
}
}]
}
{d.subArray[.sub.b.c = 1].text}
: filter using infinite object path depth. Alternative syntax without a dot is also accepted ({d.subArray[sub.b.c = 1]}
) for backward compatibility{d.subArray[i = .sub.b.c].text}
: filter using dynamic values on the right operand{d.subArray[i = ..parent.qty].text}
: filter using dynamic values from parent objects on the right operand{d.subArray[.sub.b.c = ..parent.qty].text}
: filter using complex path for the left and right operand in the same time
This feature is really powerful but some syntax are not supported yet:{d.subArray[i = .i].text}
: using .i
to join two arrays is not supported{d.subArray[i = ..parent.arr[0].id].text}
: accessing a specific array element is not supported{d.subArray[i = ..parent.arr[.i].id]text}
: accessing a specific array element according to the current iterator is not supportedhide
conditional formatter for DOCX/ODT/PDF to hide document elements: images, paragraphs, table rows, shapes and charts. The rendering is always accurate and simpler to use compared to hideBegin/hideEnd
or showBegin/showEnd
. The first argument passed to :hide(argument1)
is the element to hide, it can be:p
to hide paragraphs, usage: {d.text:ifEM:hide(p)}
. The tag must be inside a paragraph. Every elements inside the paragraph are also removed if the condition is validated.row
to hide a table row, usage: {d.data:ifEM:hide(row)}
. The tag must be inside a table row. Every element inside the row are also removed if the condition is validated.nbrRowsToHide
: Set the number of rows to hide as a second argument {d.data:ifEM:hide(row, nbrRowsToHide)}
, such as: {d.data:ifEM:hide(row, 3)}
, meaning the current and next two rows will be removed if the condition is validated. By default, the formatter :hide(row)
hides only the current row.img
to hide pictures, usage: {d.img:ifEM:hide(img)}
. The tag must be included within the image' title, description or alternative text.chart
to hide charts, usage: {d.dataset:ifEM:hide(chart)}
. The tag must be included within the chart' alternative text.shape
to hide shape (square, circle, arrows, etc...), usage: {d.dataset:ifEM:hide(shape)}
. The tag must be included within the shape' title, description or alternative text.convCRLF
prints \n
and \r\n
as new lines in ODS template instead of stringsd.duration:formatI(patternOut, patternIn)
.
It accepts duration in milliseconds (by default), or ISO format (ex. P1Y2M3DT4H5M6S).patternOut
and patternIn
can be 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
.patternOut
can be human and human+.
Here are examples with d.duration = 3600000
:{d.duration:formatI(ms)}
=> 3600000
{d.duration:formatI(s)}
=> 3600
{d.duration:formatI(minute)}
=> 60
{d.duration:formatI(hour)}
=> 1
{d.duration:formatI(human)}
=> an hour
{d.duration:formatI(human+)}
=> in an hour
{d.duration:formatI(hour, second)}
=> 1000
:html
formatter updates:<img>
is supported and rendered into DOCX/ODT/PDF documents.<img src=""/>
width
and height
attributes provided by the HTML tag, such as <img src="" width="300" height="100"/>
.
Values must be pixels. If width
or height
attributes are missing, the size of 5cm (1.96in) is applied by default while retaining the image aspect ratio.<p> <ul> <li>content </li> </ul>
, <p><p> <p>content
)aggSum
, aggAvg
, aggMin
, aggMax
, aggCount
bindColor
can be used with conditionsbindColor
formatter replaces background and line colors of shapes in DOCX only.bindColor
tag must be written in the document (NOT in alt text of the shape):html
formatter for ODT template added in v4.0.0-beta.3 (postpone in 4.1){d.value:ifLT(10):show(0):formatN}
: formatN
works even if the condition ifLT(10)
is true{bindColor(fde9a9, hexa) = d.value:ifLT(10):show(FF00FF):ifLT(20):show(005FCF):elseShow(FFDD00)}
: conditional colors works!{bind
becomes {bindChart
. Example: {bindChart(91) = d[i].valCol1}
i
and i+1
rows and columns in related Excel spreadsheet.{bindChart
is not mandatory for DOCX because MS Word accepts Carbone tags in chart valuesWARNING: Native charts in LibreOffice and Word still need a lot of work before being stable for production
[EE] ⚡️ Carbone supports dynamic charts with two methods:
bind
to tell Carbone that the value X
in the chart must be replaced by the tag Y
Insert a sample image in your template.
Place a tag in alt text , like a dynamic image : {d.chartObj:chart}
with the formatter :chart
.
The formatter :chart
is optional if the chartObj
object contains the attribute "type" : "echarts@v5"
.
In that case, Carbone automatically considers it is a chart object instead of a dynamic image.
chartObj
must contains a compatible Echarts option.
Here is an example to draw this chart:
{
"myChart" : {
"type" : "echarts@v5", // default
"width" : 600, // default
"height" : 400, // default
"theme" : null, // default or object `theme` generated by https://echarts.apache.org/en/theme-builder.html
"option" : {
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"yAxis": {
"type": "value"
},
"series": [{
"data": [150, 230, 224, 218, 135, 147, 260],
"type": "line"
}]
}
}
}
Currently, Carbone supports only "echarts@v5" but we may support newer versions and other libraries in the future. By default, Carbone considers "echarts@v5".
Some charts have some translation: Locales supported : cs, de, en, es, fi, fr, it, ja, ko, pl, pt-br, ro, ru, si, th, zh
Rendering charts with Apache Echarts is extremely powerful and works well if all these conditions are met
If you meet some limitation, please feel free to contact us on our chat to solve the issue.
[EE] If options.isDebugActive = true
. POST /render
returns additional information in debug
sub-object:
{
"renderId" : "file.pdf",
"debug" : {
"markers" : ["{d.id}", "{d.tab[i].id}"] // all tags found in template
"sample" : { // EXPERIMENTAL
"data" : {}, // fake data generated from tags found in tags
"complement" : {} // fake complement generated from tags found in tags
}
}
}
Improve syntax error message:
[i]
tag fo one [i+1]
tag.
before []
Fix crash when repetition does not contain XML tags. For example: <w:t>{d[i].id}, {d[i+1].id}</w:t>
Fix crash when the section i+1 is duplicated like the i-th section with nested repetition and other tags
Fix crash when repetition uses direct access of sub-arrays {d.test.others[i].wheels[0].size} {d.test.others[i+1].wheels[0].size}
[EE] On-Premise Embedded Studio has new features and fixes:
Data
and Complement
are automatically generated using tags found in template if these field contain empty objectsFormatters managements has been completely rewritten, to make it faster and more reliable. Here are acceptable syntax for formatters.
For backward-compatibility: text containing single quotes are accepted if it does not contain a comma ,
before or after the single quote: anyFormatter(' text ,containing ' sin,gle ' quote ')
Dynamic parameters passed in formatters with the dot .
syntax has been improved:
.sort[12].id
.sort.[sd
, .sor]t.[sd
i
.
Example: In {d[i].cars[i].other.wheels[i].tire.subObject:add(.i):add(..i):add(...i)}
.i
matches with the index of wheels[i]
..i
matches with the index of cars[i]
...i
matches with the index of d[i]
[EE] ⚡️ New aggregator formatters : aggSum
, aggAvg
, aggMin
, aggMax
, aggCount
Data
{ brand : 'Lu' , qty : 1 , sort : 1 },
{ brand : 'Fa' , qty : 4 , sort : 4 },
{ brand : 'Vi' , qty : 3 , sort : 3 },
{ brand : 'Fa' , qty : 2 , sort : 2 },
{ brand : 'To' , qty : 1 , sort : 1 },
{ brand : 'Vi' , qty : 10 , sort : 5 }
Template => Result
Global aggregation, without iterator
{d.cars[].qty:aggSum}
=> 21{d.cars[].qty:aggAvg}
=> 3.5{d.cars[].qty:aggMin}
=> 1{d.cars[].qty:aggMax}
=> 10{d.cars[].qty:aggCount}
=> 6Global aggregation, with array filters
{d.cars[sort>1].qty:aggSum}
=> 19{d.cars[sort>1].qty:aggAvg}
=> 4.75{d.cars[sort>1].qty:aggMin}
=> 2{d.cars[sort>1].qty:aggMax}
=> 10{d.cars[sort>1].qty:aggCount}
=> 4Global aggregation, with array filters, with other formatters
{d.cars[sort>1].qty:mul(.sort):aggSum:formatC}
=> 81.00 €{d.cars[sort>1].qty:mul(.sort):aggAvg:formatC}
=> 13.50 €{d.cars[sort>1].qty:mul(.sort):aggMin:formatC}
=> 1.00 €{d.cars[sort>1].qty:mul(.sort):aggMax:formatC}
=> 50.00 €{d.cars[sort>1].qty:mul(.sort):aggCount:formatC}
=> 6.00 €- [EE] 🤩 Aggregators can also be used in a loop to compute sub-totals and cumulative totals (or "running totals"), with custom grouping clause (partition)
- By default if not grouping clause is defined:
- Sum by departments of all people's salary:
- `{d.departments[i].people[].salary:aggSum}`
- `{d.departments[i].people[].salary:aggSum(.i)}` (alternative)
- Global sum of all departments, all people's salary
- `{d.departments[i].people[i].salary:aggSum}`
- `{d.departments[i].people[i].salary:aggSum(0)}` (alternative)
- Cumulative total (or "running total") by departments of all people's salary:
- `{d.departments[i].people[].salary:cumSum}`
- Cumulative total (or "running total") of all departments, all people's salary
- `{d.departments[i].people[i].salary:cumSum}`
- You can change the partition with dynamic parameters like that
- Sum by people by age, regardless of departments
- `{d.departments[i].people[i].salary:aggSum(.age)}`
- Sum by people by age and gender, regardless of departments
- `{d.departments[i].people[i].salary:aggSum(.age, .gender)}`
idTemplate
is not validHEAD /render/:renderId
: The request don't delete the generated document anymore.GET /template/:templateId
: If the template doesn't exist, the statusCode 404 is returned instead of 400.POST /render/:renderId?download=true
DELETE /template/:templateId
: If there isn't a template, Carbone does not remove any template but will still respond that the command was successful with a code 200
.svg
option to improve print quality: {d.number:barcode(qrcode, svg:true)}
getPublicKey
must take two arguments (err, publicKeys)
.
If the first argument contains an error, the bad token is kept in quarantine area for 60s instead of infinitely.Carbone
when webhook is called.DEL /template
is called and the template is already deleted on local storage. It may be already deleted by the plugin.convCRLF
before :html
formatter to convert
to <br>
add()
, mul()
, sub()
and div()
accept simple mathematical expressions inside parenthesis.{d.val:add(.otherQty + .vat * .price - 10 / 2)
+, *, -, /
are allowed, without parenthesis*
, /
) has higher precedence than the addition/substration operator (+
, -
) and thus will be evaluated first.CARBONE_EE_LICENSE
, or --license
CLI options:barcode
options as second argument. It should keep the format "key:value", such as: {d.number:barcode(qrcode, width:300, height:100, includetext:false)}
. Options available:width
Width a number as millimeters. Example: {d.number:barcode(qrcode, width:300)}
height
Height a number as millimeters. Example: {d.number:barcode(qrcode, height:10)}
scale
Quality of the barcode as a number between 1
to 10
. The default value is 3
. Example: {d.number:barcode(qrcode, scale:3)}
includetext
Show the text and takes a Boolean as value, true
or false
. Example: {d.number:barcode(qrcode, includetext:false)}
.textsize
Number to change the size of the text. Example: {d.number:barcode(qrcode, textsize:20)}
textxalign
Change the alignment of the text horizontally. Takes only 4 values: left
, center
, right
, or justify
. The default value is center. Example: {d.number:barcode(qrcode, textxalign:right)}
textyalign
Change the alignment of the text vertically. Takes only 3 values: below
, center
, or above
. The default value is below. Example: {d.number:barcode(qrcode, textyalign:above)}
.rotate
Rotate the barcode and the text. Takes only 4 values (case insensitive): N
for not rotated, R
for 90 degree right rotation, L
for 90 degree left rotation, I
of 180 degree rotation. Example: {d.number:barcode(qrcode, rotate:R)}
or {d.number:barcode(qrcode, rotate:l)}
.barcolor
Color of bars as hexadecimal #RRGGBB
. Example: {d.number:barcode(qrcode, barcolor:#1FDE25)}
. Note: 6 characters required, and case insensitive.textcolor
Color of the text as hexadecimal #RRGGBB
. Example: {d.number:barcode(qrcode, textcolor:#1FDE25)}
. Note: 6 characters required, and case insensitive.backgroundcolor
Color of the background as hexadecimal #RRGGBB
. Example: {d.number:barcode(qrcode, backgroundcolor:#1FDE25)}
. Note: 6 characters required, and case insensitive.eclevel
Specify the error correction level: L
for Low, M
for Medium (default), Q
for Quality and H
for High. Option ONLY FOR QRCODES, Micro QR Code, GS1 QR Code, HIBC QR Code, or Swiss QR Code.:html
formatter are kept on the generated document: Right-to-left text, and text/background colors.converterFactoryTimeout
updates also the HTTP socket timeout accordingly{d[i+1*qty]
options.isDebugActive
. If true, POST /render
returns additional information in debug
sub-object:{
"renderId" : "file.pdf",
"debug" : {
"markers" : ["{d.id}", "{d.tab[i].id}"] // all tags found in template
}
}
:html
formatter is not a string Now, Carbone generates a unique name for each image with the format "carbone-image-<counter>".
POST /render/:templateID
with the JSON dataset into the body request AND you have to insert the header carbone-webhook-url
as a callback URL. It is an endpoint of your server listening when a document is rendered.GET /render/:renderID
and voilà! [
{ "id" : "A", "qty" : 2 },
{ "id" : "B", "qty" : 3 },
{ "id" : "C", "qty" : 0 },
{ "id" : "D", "qty" : 1 }
]
Template: {d[i].id} - {d[i+1*qty].id}
Result: A - A - B - B - B - D
{d.value:barcode(type)}
.:barcode
formatter as a first argument: ean5
, ean2
, ean13
, ean8
, upca
, upce
, isbn
, ismn
, issn
, code128
, gs1-128
, ean14
, sscc18
, code39
, code39ext
, code32
, pzn
, code93
, code93ext
, interleaved2of5
, itf14
, identcode
, leitcode
, databaromni
, databarstacked
, databarstackedomni
, databartruncated
, databarlimited
, databarexpanded
, databarexpandedstacked
, gs1northamericancoupon
, pharmacode
, pharmacode2
, code2of5
, industrial2of5
, iata2of5
, matrix2of5
, coop2of5
, datalogic2of5
, code11
, bc412
, rationalizedCodabar
, onecode
, postnet
, planet
, royalmail
, auspost
, kix
, japanpost
, msi
, plessey
, telepen
, telepennumeric
, posicode
, codablockf
, code16k
, code49
, channelcode
, flattermarken
, raw
, daft
, symbol
, pdf417
, pdf417compact
, micropdf417
, datamatrix
, datamatrixrectangular
, datamatrixrectangularextension
, mailmark
, qrcode
, swissqrcode
, microqrcode
, rectangularmicroqrcode
, maxicode
, azteccode
, azteccodecompact
, aztecrune
, codeone
, hanxin
, dotcode
, ultracode
, gs1-cc
, ean13composite
, ean8composite
, upcacomposite
, upcecomposite
, databaromnicomposite
, databarstackedcomposite
, databarstackedomnicomposite
, databartruncatedcomposite
, databarlimitedcomposite
, databarexpandedcomposite
, databarexpandedstackedcomposite
, gs1-128composite
, gs1datamatrix
, gs1datamatrixrectangular
, gs1qrcode
, gs1dotcode
, hibccode39
, hibccode128
, hibcdatamatrix
, hibcdatamatrixrectangular
, hibcpdf417
, hibcmicropdf417
, hibcqrcode
, hibccodablockf
, hibcazteccode
ean8
, ean13
, ean128
, code39
.2021-11-18T08:05+0000
-> Europe/London
-> Thursday, November 18, 2021 8:05 AM
<paragraph>A rocket is made of {d.data:html} {d.details}, this is cool!</paragraph>
will be transform into 3 paragraphs on the generated report <paragraph>A rocket is made of </paragraph><paragraph>{d.data:html}</paragraph><paragraph> {d.details}, this is cool!</paragraph>
.[i] / [i+1]
.null
for the attribute complement
in options
{d.surrounding[i].subArray[0].subObject.id}
, within a surrounding loopimage/png; charset=utf-8
for dynamic image replacementapplication/json
and the template
must be sent in base64 in the body { "template" : "pure base64 or data-URI scheme in base64"}
W
to get the week number in formatD
formatterRelease May 10th 2021
Fix broken Excel files. It removes the alert which appears sometime when opening the file with MS Excel.
Update DayJS dependency from 1.9.6 to 1.10.4.
Fix date formatters (formatD
...) and number formatters (formatN
...) when the country code is used in options.lang
.
It accepts lower case or upper case for the locale.
Example:
Data:
{
date : '20140131 23:45:00',
price : 1000.1234
}
Template: {d.date:formatD(dddd)} {d.price:formatN()}
Result:
de-DE
=> Friday 1.000,123
(before) Freitag 1.000,123
(after)fr-fr
=> Friday 1 000,123
(before) vendredi 1 000,123
(after)Fix number formatting for these locales: nl-be, am, ar, ar-dz, ar-bh, ar-eg, ar-iq, ar-jo, ar-kw, ar-lb, ar-ly, ar-ma, ar-om, ar-qa, ar-sa, ar-sy, ar-tn, ar-ae, ar-ye, az-az, bn, bs, zh-cn, zh-hk, zh-mo, zh-sg, zh-tw, hr, da, en-au, en-bz, en-ca, en-cb, en-in, en-ie, en-jm, en-nz, en-ph, en-tt, fa, fr-lu, de-li, de-lu, de-ch, el, he, hi, id, it-it, it-ch, ja, kn, ko, ms-bn, ms-my, ml, mr, ro-mo, ro, ru-mo, sr-sp, sl, es-ar, es-bo, es-cl, es-co, es-do, es-ec, es-sv, es-gt, es-hn, es-mx, es-ni, es-pa, es-py, es-pe, es-pr, es-es, es-uy, es-ve, sw, ta, te, th, tr, uz-uz, vi, de, es, it
Example:
de
=> Freitag 1,000.123
(before) Freitag 1.000,123
(after):imageFit
formatter with the contain
property.:imageFit(fillWidth)
to fill the full width of the template image while keeping aspect ratio of the inserted image.
Before fixing the bug with contain
property, it was more or less the default behaviour of Carbone before this version.
So, fillWidth
becomes the default option to avoid changing the style of existing reports.:defaultURL()
: if a dynamic hyperlink or a HTML anchor tag is injected into a report and the URL verification fails, the formatter is used to replace the default error URL. Example to use it with and HTML formatter: {d.content:defaultURL(https:url.of.your.choice):html}
. The :defaultURL
should be placed before the :html
formatter.:html
formatter stability when using special characters such as "'<>& for ODT and DOCX reports.i+1
row contains some tags coming from parent object or condition blocks (rare){d.content:html}
, the font & size will be applied to the whole rendered HTMLdata.myBoolean = true
=> d.array[i, myBoolean=true]
=> truedata.myBoolean = "true"
=> d.array[i, myBoolean=true]
=> truedata.myBoolean = "false"
=> d.array[i, myBoolean=true]
=> falsedata.myBoolean = false
=> d.array[i, myBoolean=true]
=> falsedata.myBoolean = "true"
=> d.array[i, myBoolean='true']
=> truedata.myBoolean = true
=> d.array[i, myBoolean='true']
=> false:html
formatterd.array[i, myBoolean=true]
:html
for DOCX / ODT templates:ol
, ul
, p
, ul
, ol
, li
and a
tagsconvert
formatter for some clientswriteTemplate(err, newFilename)
👋🏻 NOTE: This version contains breaking changes of undocumented features. So if you use only documented features so far, you should not be concerned by these breaking changes.
⚡️ Manage timezone + new date formatters + switch from MomentJS to DayJS
If not defined by you in options.complement
, {c.now}
returns the current date in UTC.
[BREAKING CHANGE]: remove old date formatter which were not documented: format
, parse
, addDays
and convert
.
You should use formatD
instead and new formatters below. They were very old formatters, the chance you use them is low because you had to
look into the source code to know their existence.
New formatters:
addD(amount, unit [, patternIn])
: add days, month to a date. formatD
can be used after without specifying patternInsubD(amount, unit [, patternIn])
: subtract days, month to a date. formatD
can be used after without specifying patternInstartOfD(amount, unit [, patternIn])
: Set a date to the start of a unit of time. formatD
can be used after without specifying patternInendOfD(amount, unit [, patternIn])
: Set a date to the end of a unit of time. formatD
can be used after without specifying patternIn[BREAKING CHANGE]: We try to stay as close as possible as the previous parsing algorithm.
But 1997-12-17 07:37:16-08
is not accepted anymore without specifying an input pattern or writing 1997-12-17 07:37:16-08:00
Accept a new options to set the timezone
of formatD
. Examples:
Data
{
date : '2020-11-28T21:54:00.000Z', // ISO 8601 UTC
dateWTZ : '2020-11-28T21:54:00', // without timezone
dateTZ : '2020-11-28T21:54:00-04:00', // with America/New_York timezone offset
dateUnix : 1606600440 // UNIX timestamp in seconds UTC of 2020-11-28T21:54:00.000Z
}
Template => Result
if options.timezone = 'Europe/Paris'
(default)
{d.date:formatD(LLLL)}
=> Saturday, November 28, 2020 10:54 PM
{d.dateWTZ:formatD(LLLL)}
=> Saturday, November 28, 2020 9:54 PM
{d.dateTZ:formatD(LLLL)}
=> Sunday , November 29, 2020 2:54 AM
{d.dateUnix:formatD(LLLL, X)}
=> Saturday, November 28, 2020 10:54 PM
if options.timezone = 'America/New_York'
{d.date:formatD(LLLL)}
=> Saturday, November 28, 2020 4:54 PM
{d.dateWTZ:formatD(LLLL)}
=> Saturday, November 28, 2020 3:54 PM
{d.dateTZ:formatD(LLLL)}
=> Saturday, November 28, 2020 8:54 PM
{d.dateUnix:formatD(LLLL, X)}
=> Saturday, November 28, 2020 4:54 PM
List of timezone: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
Fix: if a path does not exist inside a formatter argument, it returns an empty string instead of the error "[[C_ERROR]] attribute_name not defined". It fixes some weird behaviour with ifEM formatters
[EE] Feature: cells colors on ODT/DOCX report can be changed dynamically with the "bindColor" tag.
[EE] ODT Improvement: the "bindColor" tag will not remove other styles than colors.
Accepts to convert the first page of docx or odt templates into a JPEG file with converTo : 'jpg'
Improve HTML type detection. Accepts html5 without doctype.
[EE] Fix Carbone tag inside ODT text box
Adding padl
and padr
string formatter.
Fix doc issue on carbone website
Accepts Adobe Indesign IDML file as a template
[EE] Dynamic hyperlinks: it is possible to insert hyperlinks into elements (text, image, list, tables, ...). Right click an element, select "hyperlinks", insert the tag and validate. It is working with ODS, ODT, and DOCX reports. The compatibility is limited for XLSX documents: It is not possible to create a list of hyperlinks and the tag should not be written with curly braces, example: a typical {d.url}
should be only d.url
. If http://
appears before d.url
, it is also valid.
Improve the parsing processing by moving the function "removeXMLInsideMarkers" before the building stage.
Support officially to embed translations tags inside other tags: {d.id:ifEq(2):show( {t(Tuesday)} ) }
Performance: reduce disk IO when converting document
Performance: deactivate image compression by default to speed up PDF conversion
[BREAKING CHANGE]: remove the possibility to use convertTo.formatOptionsRaw
for CSV export. This feature was not documented
and can lead to security issues. Use convertTo.formatOptions
instead.
new paramater in Carbone.set
renderPath
: Carbone.set
can changes the default path where rendered files are temporary saved. By default, it creates the directory `carbone_render` in Operating System temp directory.
It creates the path automatically
new paramater in Carbone.render
renderPrefix
: If defined in options
object. Carbone.render
returns a file path instead of a buffer, and it adds this prefix in the rendered filename The generated filename contains three parts:
- the prefix
- a secure Pseudo-Random part of 22 characters
- the report name, encoded in specific base64 to generate safe POSIX compatible filename on disk
`/renderpath/<prefix><22-random-chars><encodedReportName.extension>`
This filename can be decoded with the function `Carbone.decodeOuputFilename(pathOrFilename)`.
It is the user responsability to delete the file or not.
New function decodeOuputFilename()
: when renderPrefix
is used, the returned filename can be parsed with this function.
It decodes filename an returns an object with two parameters
```js
{
extension : 'pdf', // output file extension
reportName : 'decoded report name' // reportName
}
```
[BREAKING CHANGE]: Carbone.convert
function signature has changed. Now, it accepts the same options
as Carbone.renders:
You must use Carbone.convert(fileBuffer, options, callback)
instead of Carbone.convert(fileBuffer, convertTo, options, callback)
[EE] Returns errors when Carbone cannot dynamically replaces images (ex. images with absolute positions) instead of generating a bad report
[EE] Fix some PDF options. Integer values were not taken into account.
[EE] Add the possibilty to generate JPG or PNG of the first page of a document
Available options for jpg/png
:
convertTo : {
formatName : 'jpg', // or png
formatOptions : {
Quality : 90, // [JPG ONLY] From 1 (low quality, high compression, low size) to 100 (high quality, low compression, high size
PixelWidth : 100, // Image width as pixels
PixelHeight : 100, // Image height as pixels
Compression : 90, // [PNG ONLY] From 0 (compression disabled) to 9 (high compression, low size)
Interlaced : 0, // [PNG ONLY] 0 not interlaced, 1 enterlaced (higher size)
ColorMode : 0 // 0 Colors, 1 Greyscale
}
}
🎁 [EE] DOCX/ODT New feature: Support HTML rich content, by adding the :html
formatter, it is possible to render the following HTML tag:
<br>
/<b>
/<strong>
/<i>
/<em>
/<u>
/<s>
/<del>
.
Unsupported tags and tags attributes are skipped and not rendered.
HTML entities are accepted.
[EE] New feature: Dynamic pictures are supported on ODG and ODP files. It is not possible to create loops.
[EE] New Carbone Render On-Premise
arrayJoin(
):convCRLF
. Now it works in carbone v2.x.x like in v1.x.x.hardRefresh
: The content of the report is refreshed at the end of the Carbone process. For example, it can be used to refresh the table of content or update calculations. The option convertTo
has to be defined.{d.array[i].nestedArray[i=0].id}
{d.myArray}
Release June 28th, 2020
🚀 Accepts dynamic variables in all formatters!
Carbone passes data to formatters if parameters start with a dot .
and is not surrounded by quotes. Here is an example:
Data
{
id : 10,
qtyA : 20,
subObject : {
qtyB : 5,
qtyC : 3
},
subArray : [{
id : 1000,
qtyE : 3
}]
}
Template => Result
do mathematical operations:
{d.subObject.qtyB:add(.qtyC)}
=> 8 (5+3)
read parent attributes if you use two dots, grandparents if you use three dots, etc...
{d.subObject.qtyB:add(.qtyC):add(..qtyA)}
=> 28 (5+3+20)
read parent objects and their children attributes (no limit in depth)
{d.subArray[i].qtyE:add(..subObject.qtyC)
=> 6 (3+3)
It returns an error if the attribute does not exist
{d.subArray[i].qtyE:add(..badAttr)
=> [[C_ERROR]] badAttr not defined
You cannot access arrays
{d.subObject.qtyB:add(..subArray[0].qtyE)}
=> [[C_ERROR]] subArray[0] not defined
⚡️ New conditional formatters, and a new IF-block system to hide/show a part of the document
ifEQ (value)
: Matches values that are equal to a specified value, it replaces ifEqual
ifNE (value)
: Matches all values that are not equal to a specified valueifGT (value)
: Matches values that are greater than a specified value.ifGTE (value)
: Matches values that are greater than or equal to a specified value.ifLT (value)
: Matches values that are less than a specified value.ifLTE (value)
: Matches values that are less than or equal to a specified value.ifIN (value)
: Matches any of the values specified in an array or string, it replaces ifContain
ifNIN (value)
: Matches none of the values specified in an array or stringifEM (value)
: Matches empty values, string, arrays or objects, it replaces ifEmpty
ifNEM (value)
: Matches not empty values, string, arrays or objectsand (value)
: AND operator between two consecutive conditional formattersor (value)
: (default) OR operator between two consecutive conditional formattershideBegin
and hideEnd
: hide text block between hideBegin and hideEnd if condition is trueshowBegin
and showEnd
: show a text block between showBegin and showEnd if condition is trueshow (message)
: print a message if condition is trueelseShow (message)
: print a message if condition is falselen()
: returns the length of a string or array.No formatters can be chained after hideBegin
, hideEnd
, showBegin
, showEnd
.
These new formatters replace the old ones ifEqual
, ifEmpty
and ifContain
. We keep these old formatters
for backwards compatibility. You should avoid using them in new templates.
Data
{
id : 10,
qtyA : 20
}
Template => Result
print simple message according to the result of a condition
{d.id:ifEQ(10):show('hey')}
=> hey
print simple message according to the result of multiple conditions, combining with dynamic variables! 🤩
{d.id:ifEQ(10):and(.qtyA):ifEQ(20):show('hey'):elseShow('hide')}
=> hey
hide or show a block of text in the document ⚡️
{d.id:ifEQ(10):showBegin}
block of text {d.id:showEnd}
=> block of text
{d.id:ifEQ(12):showBegin}
block of text {d.id:showEnd}
=>
A smart and generic algorithm detects automatically pattern (paragraph, bullet-list, etc) to remove in document even if the conditional block
is not placed correctly in XML. For example: BEGIN<p> blabla END</p>
becomes BEGIN<p> blabla </p>END
.
It improves the final result by removing empty spaces in document.
☀️ Accepts to iterate on attributes of objects as is if it was an array
{
myObject : {
paul : '10',
jack : '20',
bob : '30'
}
}
In the report:
{d.myObject[i].att} {d.myObject[i].val}
{d.myObject[i+1].att} {d.myObject[i+1].val}
You can even access nested objects and nested arrays inside val
: {d.myObject[i].val.myArray[i].id}
Fix: avoid crashing when a static image was inserted in a loop in MS Word templates
Update totals in ODS and XSLX files
formatC
supports many crypto currencies
Fix: Accepts comma in formatter parameters such as {d.sentenceExist:ifEqual(1, 'Some sentence, some more sentences.')}
Fix: nested array in XML (but not in JSON) was not printed correctly
{d.countries[i].name}
{d.movies[i].subObject.name}
{d.movies[i+1].subObject.name}
{d.countries[i+1].name}
Fix: avoid crashing when a sub-object is null or undefined in data
Fix: avoid crashing when the parent object of an array is null or undefined in data
Eslint code + add eslint tools
Fix: accepts dashes characters in JSON data. Before, Carbone crashes when using {d.my-att-with-dash}
Fix: avoid crashing when a XLSX template contains charts
Beta: supports dynamic charts rendering in XLSX if these conditions are met:
Fix: accepts white-space in array filters with simple quote and double quotes
Example: {d.cars[i, type='Tesla car'].name}
`{d.cars[i, type="Tesla car"].name}`
Fix LibreOffice detection on Windows
Remove compatibility with older NodeJS versions (lower than 10.15.0)
Upgrade some dependencies (moment, debug, yauzl) and remove useless ones (should)
Accepts non-alphanumeric characters in variables names, values, ... For example, {d.i💎d}
is allowed
Fix many security issues and reduce memory consumption
Fix crash when tags are next to each over {d.id}{d.other}
in many situations:
Fix crash when some documents like DOCX contain images in repetition section
Accept direct access in arrays such as {d.myArray[2].val}
instead of {d.myArray[i=2].val}
Fix crash when two consecutive arrays, nested in object, were used
Remove useless soft-page-break in ODT documents as suggested by the OpenDocument specification
LibreOffice 6+ has a memory leak. So Carbone automatically restarts LibreOffice after a certain amount of document conversion.
The number of conversions depends on new parameters factoryMemoryFileSize
and factoryMemoryThreshold
Add conversion timeout parameter converterFactoryTimeout
(60s by default).
It kills LibreOffice if the conversion is too long and returns an error
Remove deprecated NodeJS "new Buffer"
Fix: avoid crashing if a object/array is null or undefined. Print empty text instead.
Fix: variables, which begin by the same characters, were not detected correctly since NodeJS 11
[EE] Image processing completely rewritten
[EE] Dynamic images improvements: it is possible to insert images into ODT
, ODS
, XLSX
and DOCX
by passing a public URL or a Data URLs. For the 2 solutions, you have to insert a temporary picture in your template and write the tag as an alternative text. Finally, during rendering, Carbone replaces the temporary picture by the correct picture provided by the tag.
The place to insert the tag on the temporary picture may change depends on the file format:
The accepted images type are: png
, jpeg
/jpg
, gif
, svg
If an error occurs for some reason (fetch failed, image type not supported), a replacement image is used with the message "invalid image".
[EE] dynamic images: new formatter :imageFit()
only available for DOCX
and ODT
files. It sets how the image should be resized to fit its container. An argument has to be passed to the formatter: contain
or fill
. If the formatter is not defined, the image is resized as contain
by default.
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 sized to fill the element’s content-box (the temporary image). The entire image will fill the box of the previous image. If the object's aspect ratio does not match the aspect ratio of its box, then the object will be stretched to fit.example: {d.myImage:imageFit(contain)}
or {d.myImage:imageFit(fill)}
[EE] Added Libreoffice export filters for PDF rendering. To apply filters to a PDF, it is possible to assign an object to convertTo
with formatName:'pdf'
and formatOptions
an object with the specified filters. The filter list is available on the documentation. Here is an example of a PDF with a password and a watermark:
options = {
convertTo: {
formatName: 'pdf',
formatOptions: {
EncryptFile : true,
DocumentOpenPassword : 'QWERTY1234',
Watermark : 'Watermark Carbone'
}
}
}
arrayMap()
if used with an array of strings or integerconvCurr(targetCurrency, sourceCurrency)
to convert from one currency to anotherformatN()
format number according to the locale (lang). Examples:toFixed(2):toFR
can be replaced by formatN(2)
formatC()
format currency according to the locale and the currencytoFixed(2)} {t(currency)}
can be replaced by formatC(2)
formatD()
format date according to the locale. Same as convDate
, but consider parameters are swapped
for consistency with formatN. Moreover, patternIn
is ISO8601 by default.convDate()
is deprecatedadd(valueToAdd)
, mul(valueToMultiply)
, sub(valueToSubstract)
,div(value)
: mathematical operationssubstr(start, end)
: slice stringscarbone.set
and carbone.render
have new optionscurrencySource
: default currency of source data. Ex 'EUR'currencyTarget
: default target currency when the formatter convCurr
is used without targetcurrencyRates
: rates, based on EUR { EUR : 1, USD : 1.14 }option.lang
must use the i18n format 'fr-fr' instead of just 'fr'soffice
and python
across all operating platforms. Done by Robert Kawecki (@rkaw92).{
before the tagsd
or c
like this {d:ifEmpty('yeah')}
...{d.cities[i=0].temperature}
)unaccent
to remove accent from stringcarbone.set
does not overwrite user-defined translations{d[i].brand} , {d[i+1].brand}
unaccent()
to remove accent from stringcount()
to print a counter in loops. Usage: {d[i].name:count()}
convCRLF()
to convert text, which contains
or
, into "real" carriage return in odt or docx documentcanInjectXML = true
can inject XML in documentsi
iterator{d.cities[i=-1].temperature}
shows the temperature (if the array is not empty) of the last city{d.cities[i=-2].temperature}
shows the temperature of the city before the lastcarbone.set
options.convertTo
is usedcarbone.set
takes into account changes on factories
and startFactory
parametersoptions.convertTo
equals input file extension{DSDSD-232D}
used in DOCX..
and then access children properties as usual: {d.cities[i, temp=20]..country.history.sport.value}
..
or more. Use case: conditional printing of properties using filters in nested arrays:{d.cities[i, temp=20]..countryName}
prints d.countryName
only when the temperature of cities equals 20if
, stop propagation to next formatters if the condition is trueconvEnum(d, type)
: convert enums to human readable valuesifEqual(d, value, messageIfTrue, continueOnSuccess)
: show message if d == value
, and stop propagation to next formatters unless continueOnSuccess
is trueifContain(d, value, messageIfTrue, continueOnSuccess)
: show message if d contains value
, and stop propagation to next formatters unless continueOnSuccess
is trueprint(d, message)
: print messagecarbone.renderXML(xmlString, data, options, callback)
to render XML directlycarbone.render
and carbone.renderXML
with options.lang = 'fr'
. The date formatter is automatically propagated on formatters such as convDate
carbone find :formatterName
ifEmpty
, arrayJoin
, arrayMap
, convDate
, lowerCase
, upperCase
, ucFirst
, ucWords
convert
, format
, addDays
, parse
: if the date is null or undefined these formatters return null or undefined instead of "Invalid Date"carbone.render
crash if options
contains formatName
without formatOptionsRaw
and formatOptions
options
when using convert(data, convertTo, options, callback)
function. It is mandatory for CSV files otherwise LibreOffice does not understand the file type.{
fieldSeparator : ',',
textDelimiter : '"',
characterSet : '76',
sourceExtension : '.csv'
}
Allow the comparison operator !=
in your template: {d.cars[engine.power != 3].name}
Add a newline at the end of the lang file
Fix: Translation, upper and lower case order does not depend on user's language anymore
Do not restart LibreOffice if the document is corrupted (restart it only after 10 consecutive attemps)
Return an error when the document cannot be converted
Improve stability in general
Socket connections have been improved:
Fix bugs in tests
Compatible with Node v0.12
Update zipfile dependency
Now, the repetition algorithm can flatten an array of objects!!
Example:
Datas:
```
[
{
'site' : {'label':'site_A'},
'cars' : [
{
'name': 'prius',
'spec': {'weight': 1},
'wheels':[
{'brand':'mich'},
{'brand':'cont'}
]
},
{
'name': 'civic',
'spec': {'weight': 2},
'wheels':[
{'brand':'mich'}
]
},
],
},{
'site' : {'label':'site_B'},
'cars' : [{
'name': 'modelS',
'spec': {'weight': 1},
'wheels':[
{'brand':'mich'},
{'brand':'uni' },
{'brand':'cont'}
]
}
],
}
];
```
Template:
site | car name | weight | brand |
---|---|---|---|
{d[i].site.label} | d[i].cars[i].name} | {d[i].cars[i].spec.weight} | {d[i].cars[i].wheels[i].brand} |
{d[i+1].site.label} | d[i+1].cars[i+1].name} | {d[i+1].cars[i+1].spec.weight} | {d[i+1].cars[i+1].wheels[i+1].brand} |
Result:
site | car name | weight | brand |
---|---|---|---|
site_A | prius | 1 | mich |
site_A | prius | 1 | cont |
site_A | civic | 2 | mich |
site_B | modelS | 1 | mich |
site_B | modelS | 1 | uni |
site_B | modelS | 1 | cont |
Add new conversion options for CSV export
Now it is possible to pass conversion options to LibreOffice. The attribute convertTo
can be an object instead of a string:
var _options = {
'convertTo' : {
'formatName' : 'csv',
'formatOptions' : {
'fieldSeparator' : '+',
'textDelimiter' : '"',
'characterSet' : '0'
}
}
};
carbone.render(_filePath, data, _options, function(err, result){
...
});
Add the possibility to pass raw conversion options to LibreOffice. See documentation: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Spreadsheets/Filter_Options
var _options = {
'convertTo' : {
'formatName' : 'csv',
'formatOptionsRaw' : '124,34,0'
}
};
carbone.render(_filePath, data, _options, function(err, result){
...
});
Improve LibreOffice detection algorithm for Linux. Now, it supports any version of LibreOffice
BUG fix: When two nested arrays were incremented in the same time {d.tab[i+1].subtab[i+1]}
, it could generate bad XML in certain cases