This project was born from my need to evaluate some simple mathematical expressions within AsciiDoc files and output their results in the final generated document.
It is intended to be used along with the Asciidoctor Maven Plugin, the IntelliJ AsciiDoc Plugin, or a similar tool that allows importing this dependency as an Asciidoctor Java extension.
com.lealceldeiro:asciidoc-extensions:0.*.*is compatible withorg.asciidoctor:asciidoctorj:2.5.13, Java 11com.lealceldeiro:asciidoc-extensions:1.*.*is compatible withorg.asciidoctor:asciidoctorj:3.*.*, Java 11com.lealceldeiro:asciidoc-extensions:2.2.*is compatible withorg.asciidoctor:asciidoctorj:3.*.*, Java 21com.lealceldeiro:asciidoc-extensions:2.3.*is compatible withorg.asciidoctor:asciidoctorj:3.*.*, Java 25com.lealceldeiro:asciidoc-extensions:2.4.*is compatible withorg.asciidoctor:asciidoctorj:3.*.*, Java 25
Add the dependency to your pom.xml file as follows:
<dependency>
<groupId>com.lealceldeiro</groupId>
<artifactId>asciidoc-extensions</artifactId>
<version>${asciidoc-extensions.version}</version>
</dependency>To check what the latest version you can use is, as well as for other build tools, please check https://central.sonatype.com/artifact/com.lealceldeiro/asciidoc-extensions.
That is it, the macros will be registered automatically for you once the dependency is in your project's classpath.
For more info on how it is automatically registered for you, please check the Maven Plugin Configuration section and AsciidoctorJ's Extension API.
Starting from version 2.1.0, this extension can be used along with the
IntelliJ AsciiDoc Plugin.
To know how this type of extensions can be used along with the plugin, please refer to the plugin
official documentation.
Make sure the dependency
(jar file) you use to be added to your project classpath is the one ending with
jar-with-dependencies.jar.
For example asciidoc-extensions-2.1.0-jar-with-dependencies.jar (replace 2.1.0 with the
latest version available in Maven).
It is important to notice that the jar-with-dependencies that is used for this purpose
contains a dependency to https://central.sonatype.com/artifact/org.mariuszgromada.math/MathParser.org-mXparser.
Make sure you read the section for the calc_exp macro shown below,
for more information about this.
It includes three inline macros:
calccal_datecalc_exp
and, starting from version 2.4.0, one block macro:
chart
Allows making simple calculation within the asciidoc document. Example:
// outputs 3: 1 + 2
calc:sum[1, 2]
// outputs 1: 5 - 3 - 1
calc:sub[5, 3, 1]
// outputs 12: 4 * 3
calc:multiply[4, 3]
// outputs 5: 10 / 2
calc:divide[10, 2]It allows for attributes substitution as well. Example:
= Title
:price: 50
:units: 3
// outputs 150
calc:multiply[{price}, {units}]Starting from version 2.3.0, you can now specify what rounding mode to use by setting the
attribute rounding_mode, for example:
// outputs 0.97
calc:sum[rounding_mode="CEILING", 0.471, 0.49]If not specified, by default, it uses
HALF_EVEN.
Other values can be specified as well. These can be any of the enum values specified by the RoundingMode java class.
For the time being,
the results are being returned as BigDecimals with scale 2.
I plan to provide ways to customize that, but I haven't had the time yet.
PRs are welcome.
Starting from version 2.4.1, you can style the rendered result based on the sign of the
calculation by providing the role
to apply for each case. The role names are entirely up to you (they map to your document's roles /
your PDF theme); the macro is agnostic to them:
| Attribute | Applied when the result is |
|---|---|
role_positive |
greater than zero |
role_negative |
less than zero |
role_zero |
equal to zero |
// renders 150.00 with the `success-bg` role applied
calc:sum[100, 50, role_positive=success-bg, role_negative=danger-bg]
// renders -50.00 with the `danger-bg` role applied
calc:sub[50, 100, role_positive=success-bg, role_negative=danger-bg]Each attribute is optional and independent: when the attribute matching the result's sign is not
provided (or is left blank), no role is applied. This lets you, for instance, highlight only
negative results by providing role_negative alone. Likewise, no role is applied when the result
is not a valid number (for example, a NaN result).
If the calc macro isn't provided with a valid operation, that's it,
one of sum, sub, multiply, or divide, then NaO is returned as a result.
If any of the values to be used in the operation, those within square brackets ([]), is not valid,
then NaN is returned as a result.
For instance, this call would return NaN: calc:sum["a_text", 2].
There's an exception to this rule: if the argument mode is provided with ignore_invalid, then,
invalid "number" arguments are ignored,
and the calculation is performed as if it was not provided at all.
For example:
// these two options, both, output 1, as it's equivalent to calc:sum[1]
calc:sum[1,"this is a text", mode="ignore_invalid"]
calc:sum[ignore_invalid, 1,"this is a text"]
// outputs NaN, as "this is a text" is not a valid number
calc:sum[1,"this is a text"]
// outputs NaN, as "ignore_invalid" was neither provided as a named argument nor as an unnamed one in the first position
calc:sum[1, 2, ignore_invalid]If there's any exception while doing the calculation because of an arithmetic rule,
then NaVM is returned.
For example:
// outputs `NaVM`, as division by zero is not mathematically possible
calc:divide[4, 0]
// outputs `0`, as dividing zero by another number different from zero is mathematically possible
calc:divide[0, 4]Similarly, the calc_date performs two simple operations on dates: addition and subtraction.
Examples:
// outputs 2024-01-03: 1st January 2024 + 2 days
calc_date:sum[2024-01-01, 2d]
// outputs 2024-01-06: 1st January 2024 + 5 days
calc_date:sum[2024-01-01, 5]
// outputs 2023-12-01: 1st January 2024 - 1 month
calc_date:sub[2024-01-01, 1m]As you can see from the previous examples, you can indicate the amount to add or subtract by using
d (default) for days, m for months, and y for years.
Optionally, you can modify the format of the resulting date. Any valid expression for the Java DateTimeFormatter will work. For example:
// outputs Jan 1, 2023
calc_date:sub[2024-01-01, 1y, format="MMM d, yyyy"]Additionally, you can specify the source and target zone ids for the date being handled. This is useful when you want the output date rendered in a different timezone than the one in which the original date was provided (i.e: the zone for the "machine" where the document is being rendered).
If no values are provided for the source and target zone ids, or they're invalid zone id values, then the system default zone id is used.
For example, assuming the document is being rendered at 12:24 in a machine running in the
America/Adak timezone and the target timezone is Pacific/Tarawa, then the following call
would return 2025-08-31 as the result::
// outputs 2025-08-31
calc_date:sub[2025-08-30, 0, to_zone_id="Pacific/Tarawa"]Let's see other examples, all of them assuming the document is generated at
(UTC) 2025-08-30T21:24:00.000[UTC].
For simplicity, the amount to be subtracted is always 0.:
// outputs 2025-08-31
calc_date:sub[2025-08-30, 0, from_zone_id="America/Adak", to_zone_id="Pacific/Tarawa"]// outputs 2025-08-29
// in Athens, it's 30th at 00:24:00, so when converted to UTC (21:24:00),
// then it's "moved back" to the previous day
calc_date:sub[2025-08-30, 0, from_zone_id="Europe/Athens", to_zone_id="UTC"]If the calc_date macro isn't provided with a valid operation, that's it,
one of sum, or sub, then NaO is returned as a result.
For instance, this call would return NaO: calc_date:multiply[2024-01-01, 2].
If any of the values to be used in the operation, those within square brackets ([]), is not valid,
then an appropriate value is returned.
For example,
if an invalid date is provided, then NaD is returned as a result.
For instance, this call would return NaD: calc_date:sum["a_text", 2].
Also, dates without one of the valid standard formats as described in the
Java DateTimeFormatter class will be considered as invalid.
For example, this call would return NaD: calc_date:sum[2024 01 01, 2].
In the future, I'll add support to specify the input format pattern.
PRs are welcome!
Additionally, if the output format is specified, but it's invalid.
Then NaF is returned.
For example, this call would return it: calc_date:sum[2024-01-01, 2, format="j0"]
Similarly, if the amount to be used in the operation is not a valid number
then the returned value is NaN.
For example:
// outputs NaN: 'f' is not a number, although 'y' is interpreted as years to be added
test: calc_date:sum[2024-01-01, fy]This macro also supports the mode="ignore_invalid" argument described previously.
When it's used, any invalid value will be replaced as follows:
dateis replaced byLocalDate#now()amountis replaced by0formatis replaced byDateTimeFormatter.ISO_DATE
For example:
// supposing LocalDate#now() returns 2025-01-01, then this returns 2025-01-02 as it replaces the invalid date
calc_date:sum["1st of Jan 2024", 1, mode=ignore_invalid]// this returns 2024-01-01 as `0` replaces the invalid value to add to the date
calc_date:sum[2024-01-01, xyz, mode=ignore_invalid]// these two options return 2024-01-03 as the default format replaces the invalid one
calc_date:sum[2024-01-01, 2, format="j0", mode=ignore_invalid]
calc_date:sum[2024-01-01, 2, format="j0", ignore_invalid]This macro is a wrapper around https://mathparser.org/. This means the actual logic to calculate whatever expression is passed to the macro is performed by https://mathparser.org/
Before continue reading, it is important that you understand that this software doesn’t grant you any type of license for use of https://mathparser.org/.
While this software is licensed under MIT, https://mathparser.org/ has its own License Agreement, Terms and Conditions, etc., to which you must adhere.
I kindly ask you to take 5 minutes and read their license before using this macro.
In short, if you're obliged to purchase a license from https://mathparser.org/ because of the final use you'll give to their, or this, software; then you must do so because (as stated before) the use of this library doesn’t grant you any rights over https://mathparser.org/.
Likewise, if you import this dependency in you project, but don't plan to use the calc_exp macro,
you don't have to worry about any of this.
Getting the legal bits out of our way, let's see how it can be used.
For this macro to work for you, the document in which it's used must contain a valid author
(Asciidoc :author: attribute).
Optionally, the author macro attribute can be provided instead of using the document attribute.
In both cases, it must be a value with length greater than 5 characters.
See some examples below.
If there isn't such a valid value for this attribute you'll get NaA or NaVA as a result.
There's a second mandatory value that you must provide: calc_exp_license_type, which can be
either commercial or non_commercial.
This value can be provided directly as an attribute for the macro, or at the document level.
non_commercial: indicates that you haven’t purchased any license for commercial use, from https://mathparser.org/ and that you plan to use it for non-commercial purposes.commercial: indicates that you’ve purchased a license for commercial use, from https://mathparser.org/
If there isn't any valid value provided for this attribute you'll get a NaL as a result.
These two values are used to acknowledge that you comply with the license agreement from https://mathparser.org/. See more at https://mathparser.org/mxparser-tutorial/confirming-non-commercial-commercial-use/
Example, setting the author and license type at the document level:
= My Document
Asiel Leal_Celdeiro
:calc_exp_license_type: non_commercial
// outputs 1.00
calc_exp:[1 ^ 2]Example, setting the author and license type at the macro level:
= My Document
// outputs 9.00
calc_exp:[exp=3 ^ 2, author=Johnny, calc_exp_license_type=commercial]Similar to calc, starting from version 2.3.1, you can now specify what rounding mode to use
by setting the attribute rounding_mode, for example:
// outputs 0.97
calc_exp:[exp=0.471 + 0.49, author=Johnny, calc_exp_license_type=non_commercial, rounding_mode="CEILING"]For more info about how to use the rounding_mode attribute, see section
Rounding in calc. The usage is the same as in this macro.
Similar to calc, starting from version 2.4.1, you can style the rendered result based on its
sign by setting the role_positive, role_negative, and role_zero attributes, for example:
// renders the result with `success-bg` when positive, or `danger-bg` when negative
calc_exp:[exp=3 - 9, author=Johnny, calc_exp_license_type=non_commercial, role_positive=success-bg, role_negative=danger-bg]For more info about how these attributes work, see section
Sign-based role. The usage is the same as in this macro.
If there is an invalid author value a NaA is returned.
A special case is when
the value is shorter than 5 characters, where you get a NaVA (this
special case is due to the underlying library)
Example:
// outputs NaA: if the document-level author is null or invalid
calc_exp:[exp=3 ^ 2, calc_exp_license_type=non_commercial]// outputs NaVA
calc_exp:[exp=3 ^ 2, author=John, calc_exp_license_type=non_commercial]If there is an invalid value for calc_exp_license_type,
then a NaL is reported.
Example:
// outputs NaL
calc_exp:[exp=3 ^ 2, author=Johnny, calc_exp_license_type=testing]If there is a failure while trying to evaluate the expression
then there is an NaE returned.
Example:
// outputs NaE
calc_exp:[exp=3 ^, author=Johnny, calc_exp_license_type=non_commercial]Starting from version 2.4.0, the chart block macro renders a line chart as an
embedded SVG image. Unlike the other macros, it's a delimited block (not an inline macro),
and it has no extra runtime dependencies — the SVG is built by the extension itself.
A minimal chart looks like this:
[chart,line]
.Family & work expenses per month
----
x: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
Family: ,,,5568.09,5362.59,5633.96,,,,,,
Work: ,,,357.49,354.00,353.75,,,,,,
----The example below adds a currency unit and point-labels=k (see the attributes table):
The block body is line-oriented:
-
A line starting with
x:(orlabels:) defines the x-axis labels, comma-separated. Exactly one such line is required; its number of entries isN. -
Every other
Name: v1,v2,…,vNline defines a series namedName. You can add as many series as you want. -
Values are positional and aligned to the x-axis labels. An empty entry (or a non-numeric one) is a gap: no point is drawn there, and the line skips it. This lets you fill in months as data arrives:
Family: ,,,5568.09,5362.59,5633.96,,,,,,
-
Fewer than
Nentries → the remaining positions are gaps; more thanN→ the extras are silently truncated. -
Blank lines and lines starting with
//are ignored.
The block is a verbatim block, so you reference document attributes directly and the macro
resolves them (AsciiDoc stores attribute names lower-cased; the macro looks them up
accordingly, so mixed-case references like {aprFamily} work). An unresolved reference
becomes empty — i.e. a gap. This makes charts "live" off your document data:
:aprFamily: 5568.09
:mayFamily: 5362.59
:junFamily: 5633.96
[chart,line]
----
x: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
Family: ,,,{aprFamily},{mayFamily},{junFamily},,,,,,
----All of the following are optional block attributes with sensible defaults, so the minimal form above just works:
| Attribute | Default | Meaning |
|---|---|---|
title / .Cap |
none | Chart caption (the AsciiDoc block title) |
width |
520 |
SVG width, in px |
height |
300 |
SVG height, in px |
ymin |
auto | Y-axis minimum (advisory: the axis still baselines at 0 and rounds to nice ticks) |
ymax |
auto | Y-axis maximum (advisory: rounded up to a nice tick) |
unit |
none | Suffix appended to the y-axis tick labels, e.g. " €" |
points |
true |
Whether to draw a marker at each data point |
point-labels |
none |
Value labels on points: none, full (the exact amount), or k (amounts ≥ 1000 shown approximate as ~X.XXK, 2 decimals; smaller values shown in full). Y-axis ticks are unaffected. |
For example, the sample image above is produced with:
[chart,line,unit=" €",point-labels=k]
----
x: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
Family: ,,,5568.09,5362.59,5633.96,,,,,,
Work: ,,,357.49,354.00,353.75,,,,,,
----Only the line chart type is supported for now. Any other type (e.g. [chart,pie]) logs a
warning and renders nothing, without failing the build.
The default rounding mode used to be RoundingMode.CEILING. It changed to RoundingMode.HALF_EVEN.
The default rounding mode used to be RoundingMode.CEILING. It changed to RoundingMode.HALF_EVEN.
You can contribute to this project! Read our contribution guidelines.
