Using this integration module, you can mix Jamal macro text with YAML data. To use this module, you have to add the dependency to your Maven project, as:
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-yaml</artifactId>
<version>2.8.3-SNAPSHOT</version>
</dependency>
Following that, you can use the
macros.
since 1.7.5 ?
and !
macro redefinition control
since 2.8.1 OGNL subgraph can be used as macro argument
You can use this macro to define a Yaml structure. The format of the macro is
{@yaml:define yamlMacro=
tabulated Yaml content
}
After that, the name yamlMacro
will be defined as a user-defined macro and can be used as {yamlMacro}
.
The value will replace the place of the use with the actual Yaml content.
The macro can have one argument, which is an OGNL expression.
If it is present then the macro will return part of the yaml structure pointed by the OGNL expression.
Note
|
Internally, Jamal converts the Yaml read in an object structure consisting of strings, primitives, maps, and lists.
The structure is stored in an object of the type YamlObject .
This class technically is a user-defined macro.
The yaml:define macro will register the structure among the user-defined macros.
When the name is used the same way as any other user-defined macro (without any argument), the content of the Yaml structure is converted to text.
|
The yamlMacro
is stored along with the "usual" user-defined macros.
Any usual or other user-defined macro can be redefined any number of times.
If you want to define a Yaml macro only if it was not defined prior, use the ?
after the keyword yaml:define
.
If you want to get an error message if the macro was already defined, use the !
after the keyword yaml:define
.
This functionality is implemented the same way as it is for the core built-in macro define
.
The example:
{@yaml:format prettyFlow flowStyle=BLOCK}
{@yaml:define xyz=
a: this is the string value of a
b:
- first value of b
- second value in b
c:
a: this is c.a
b: this is c.b
}\
{xyz}\
or
--
{xyz| c}\
--
will result
a: this is the string value of a
b:
- first value of b
- second value in b
c:
a: this is c.a
b: this is c.b
or
--
a: this is c.a
b: this is c.b
--
The advantage of using this macro over just writing the Yaml directly to the output is that:
Many times Yaml structures become profoundly nested, and it is not trivial to move things around. The Yaml structure is a data description structure, and many times these data structures are redundant. When editing Yaml structures, it is common to copy some part of the structure and edit its art. Using Jamal, you can solve the indentation hell as well as you can reduce the poisonous redundancy. Utilizing user-defined macros, you can use macros inside Yaml code, and at the same time, you can use Yaml code inside the macros. That way, you can pull out the part, repeat, and use only the macro as a reference.
It is a solution for strings and parts of some strings used at multiple locations in the Yaml file. User-defined macros alone do not solve the problem of overcomplexity and deep tabulation.
The Jamal Yaml module allows you to have a partially defined Yaml file to overcome this problem. When a Yaml structure is read from the text representation, it is stored in the memory as a Java object structure. It utilizes primitive values, strings, lists, and maps.
There is a particular class in the javax0.jamal.api
package, called Ref
.
When you specify a value
!ref xyz
it will create a new Ref(xyz)
object.
Jamal will interpret this object as a reference to another Yaml data structure stored as a user-defined macro with the name xyz
.
When you specify the Yaml structure, the user-defined macro xyz
does not need to be defined yet.
It may be defined, but it may also be defined later.
What is more, the macro can also be the same as the one that contains it.
These references can be recursive.
The example: .Jamal source
{@yaml:define b=- x
- y
- !ref a}
{@yaml:define a=[a,b,c]}
{b}
resulting in the output
- x
- y
- !!javax0.jamal.api.Ref
id: a
Note
|
You can notice that the macro a referenced from b is defined later than b .
|
Having these references with Jamal specific class types is of no general use.
Their value is that Jamal can resolve them by converting the references to the content of the named macro.
When you invoke the built-in macro yaml:resolve
, these references will be replaced with their actual value.
The same example as above, but resolving the Yaml macro before using it
{#yaml:define b=- x
- y
- !ref a}
{@yaml:define a=[a,b,c]}
{@yaml:resolve b}
{b}
will result
- x
- y
- - a
- b
- c
When you invoke yaml:resolve
, all the referenced Yaml macros have to be defined.
Using this reference possibility, you can have several small Yaml fragments possibly referencing each others using the !ref
.
When the small segments are done, you can apply yaml:resolve
on the root one and create the output.
Note
|
When processing Yaml input, you can use the One solution to this problem is to use a different opening and closing string that does not appear inside the Yaml output.
If you can find one for your application, you can go for it.
Usually, you cannot guarantee that none of the string fields will contain the macro opening string.
The safe solution is that these macros are defined by the Yaml built-in macros as verbatim.
If you need to evaluate the content of the Yaml structure with embedded macros you have to use the For more information on macro evaluation order, see the core documentation of Jamal. |
The use of this macro has been deprecated since Jamal version 2.0.0.
This macro will reference another Yaml definition.
With the release 2.0.0, the local tag !ref
is defined, which is a much cleaner and shorter way to reference another Yaml definition.
{@yaml:define x=
a: this is a string
b: !ref xyz}
Use this macro to resolve one or more user-defined Yaml macro.
The format of the macro is
{@yaml:resolve macroName1, macroName2, ..., macroNameX}
User-defined Yaml macros created using the define
or import
macros may reference other user-defined Yaml macros.
When you invoke the macro yaml:resolve
, it will replace the references in the Yaml macro content with the content of the Yaml macro it references.
The resolving process is recursive.
If there are any references in the referenced Yaml macro, it will also be resolved.
After resolving a macro xyz
that references the macro aqt
, the macro aqt
will also be resolved.
In some rare cases, this should not happen.
If the referenced Yaml macros should not de resolved, then the option yamlReferenceClone
should be set using the macro {@option yamlReferenceClone}
.
This option also has a local parameter alias, clone
, that can be used between (
and )
as a macro option.
The example
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a}
{#yaml:define xyz=
a: 1
b: 3
c: !ref aqt}
{@yaml:resolve (clone) xyz}
Resolved:
{xyz}
Not resolved:
{aqt}
will result
Resolved:
a: 1
b: 3
c:
z:
- a
- b
- c
Not resolved:
z: !!javax0.jamal.api.Ref
id: a
This process will not change the value of the macro aqt
.
In this case, the resolving process will create a copy of the referenced macro, and it will resolve the copy recursively.
That way, xyz
is still fully resolved and ready to be used.
The same example doing the resolve without the clone
option, however
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a}
{#yaml:define xyz=
a: 1
b: 3
c: !ref aqt}
{@yaml:resolve xyz}
Resolved:
{xyz}
Also resolved:
{aqt}
will result
Resolved:
a: 1
b: 3
c:
z:
- a
- b
- c
Also resolved:
z:
- a
- b
- c
Note
|
When a macro is resolved, it will remember that it was already resolved and will not execute the resolve process anymore.
It also means that calling yaml:resolve with the clone option on xyz and then calling it again without it will not resolve the referenced aqt .
Unless you have a specific need, use the yaml:resolve macro without cloning.
|
There is another option that alters the behaviour of the resolving process.
This is yamlResolveCopy
with the alias copy
.
This option creates a copy of the referenced structures, cloned or not.
To understand this, we can have a look at the following example:
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a
y: !ref a
}
{@yaml:resolve aqt}
{aqt}
will result
z: &id001
- a
- b
- c
y: *id001
When the underlying Snake Yaml library generates the text format of the Yaml data it realizes that both z
and y
fields refer to the same object.
Thus, SnakeYaml generates a label, something like &id001
at the first occurrence and instead of repeating the same structure it on second occasion it only references that as *id001
.
The resolving process can circumvent this creating a copy for every reference.
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a
y: !ref a
}
{@yaml:resolve (copy) aqt}
{aqt}
will result
z:
- a
- b
- c
y:
- a
- b
- c
Note that copy
and clone
are not the same.
You can use the clone
option together or without copy
and also the other way around.
The implementation of copy resolution can handle recursive data structures and it will generate references into the output.
For example
{#yaml:define a=[ a, b ,c, !ref a]}
{@yaml:resolve (copy clone) a}
{a}
works as expected
&id002
- a
- b
- c
- *id002
However, when trying to resolve the following:
{#yaml:define a=[ a, b ,c, !ref a, !ref a]}
{@try! {@yaml:resolve (copy clone) a}}
{a}
then the result is
Jamal source seems to have infinite recursion
- a
- b
- c
- !!javax0.jamal.api.Ref
id: a
- !!javax0.jamal.api.Ref
id: a
Here we got an error message from the macro try
, and the Yaml structure stored in a
remained unresolved.
There is no import macro to read Yaml formatted data from a file.
If you want to read data from a file, you should combine the yaml:define
and the core include
macros.
For example, there is a resource file src/test/resources/sample.yaml
in the project where this documentation is compiled.
It can be referenced from the test execution, which also converts this document as res:sample.yaml
.
The content of this file is
# this is a sample Yaml file that the test TestImport imports
# note that this file is a pure YAML file and no Jamal macros are in it
# contains user defined macro reference `a`
&id
a: this is {a}
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id
You can use the following structure to read it from the file and assign the Yaml data to the macro aqt
:
{#yaml:define aqt={@include [verbatim] res:sample.yaml}}
{aqt}
which will result
&id003
a: this is {a}
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id003
The file has to be a Yaml formatted file, and it should not contain any Jamal macro.
(If it does, it will be treated as raw data and will not be macro processed by Jamal.)
If you want to read a Jamal formatted Yaml file, you must include it using the core include
macro without the [verbatim]
option.
Note
|
The built-in core macros use the [ and ] characters to enclose the options.
Other packages usually use ( and ) .
|
The file src/test/resources/sample.yaml.jam
contains Jamal macros:
# this is a sample Yaml file that the test TestImport imports
# note that this file is a pure YAML file and no Jamal macros are in it
# contains user defined macro reference `a`
&id
a: this is {a}
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id
You can include it with evaluation using the following macro sequence.
{@define a=wuff wuff}
{#yaml:define h={@include res:sample.yaml.jam}}
{h}
which will result:
&id004
a: this is wuff wuff
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id004
The macro yaml:isResolved
results true
or false
if the Yaml macro given as argument is either resolved or not.
The syntax of the macro is
{@yaml:isResolved macro_name}
Example:
{@yaml:define a=
a: this is a
b: this is b
}
{@yaml:isResolved a}
{@yaml:resolve a}\
{@yaml:isResolved a}
results
false
true
Note that the example Yaml structure does not need resolution. This macro does not test the structure. It simply tells that the structure went through the resolve process or not.
Usually there is narrow use of this macro.
There is no penalty invoking resolve
on a structure that was already resolved.
The macro resolve does not re-run the resolution process for a structure that was already resolved.
Other macros that need resolved structures automatically invoke resolving.
This macro will fetch one value from a Yaml structure. This can be useful when you want to document some configuration or other data structure that is present as a Yaml file in your project. In that case you can import the Yaml structure into your Jamal document and refer individual values in it. The format of the macro is:
{@yaml:get (from=yamlMacro) OGNL-PATH}
The option from
names a Yaml user defined macro, where the Yaml structure was loaded.
It can also be defined outside as a user defined macro of the name yamlDataSource
.
This is useful when you want to retrieve multiple values from the same data structure.
The OGNL-PATH
is a Object Graph Navigation Library Path.
The functionality to fetch a value is implemented using the Apache Commons OGNL library.
For more information about the OGNL language visit the web site https://commons.apache.org/proper/commons-ognl/index.html.
When getting a value out of a Yaml user defined macro the macro will automatically be resolved.
The resolution can be cloning or in-place.
To control the resolution process the same options can be used as for the resolve
macro.
The macro yaml:set
can define a user-defined YAML macro from an already existing YAML macro.
It is similar to yaml:define
but this macro does not parse a text and does not interpret it as YAML formatted text.
Instead, it uses an already defined YAML user-defined macro and uses some part of it.
It assigns the specific part to a new user-defined macro.
The syntax of the macro is
{@yaml:set (options) macroName=OGNL}
-
The
options
are the same as in the macroyaml:get
:-
yamlResolveClone
(aliasclone
) to clone -
yamlResolveCopy
(aliascopy
) to copy resolve -
yamlDataSource
(aliasfrom
) the name of the user-defined macro which is the source of the data
-
If the from
value is missing then the macro interprets the OGNL
expression start as the name of the macro.
In this case the first character of the expression has to be /
.
It essentially will start with /xxxx.
where xxxx
is the name of the macro.
The macroName
is the name of the macro to assign the new object value to.
{@yaml:define a=
a: alma
b:
c: 3
d:
- 1
- 2
- q:
h: deep h}
{@yaml:set s=/a.b.d[2].q.h}
{@yaml:set (from=a) r=b.d[2].q.h}
{s}
{r}
will result
deep h
deep h
When calling set the new macro will refer to the same object as the original one. It does not create a copy of the object pointed by the OGNL expression. This is demonstrated with the followinf example:
{@yaml:define a=
a: alma
b:
c: 3
d:
- 1
- 2
- q:
h: shallow dust}
{@yaml:set s=/a.b.d[2].q}
{@yaml:add to=a.b.d[2].q key=z
deep water
}
{s}
We set the reference to the object q
.
Then we add a new key-value pair to the object q
using the macro a
.
In the output the result is modified in the macro s
as well.
h: shallow dust
z: deep water
It is the behavior even of the clone
or copy
options are used.
These options are controlling the resolution process and not the object creation.
The macro yaml:add
can modify an already parsed Yaml data structure.
You can add elements to lists or maps inside the Yaml structure.
The syntax of the macro is
{@yaml:add options
yaml data structure
}
The yaml data structure is the textual representation of the Yaml data to be hooked on the already existing data structure.
The options present on the same line as the macro keyword yaml:add
and the yaml data structure starts on the second line.
The possible options are:
-
yamlDataTarget
(aliasto
) must be specified and should define the point where the new data structure is added. The keywordyamlDataTarget
can also be a user defined macro. The aliasto
can only be used in the macro use. UsingyamlDataTarget
defined as a user-defined macro makes sense when there are several additions to the same point. The format of the option ismacroName.ognl expression
. The name of the macro that holds the current data structure to be modified is at the start of theto
string. It is separated by a.
dot character from the Ognl expression that identifies the part of the structure to be modified. If there is no.
in this parameter then the root of the structure is used. -
key
should only be specified when adding new data to aMap
. The value of this option will be the key used in theMap
. If data with the key already exists it will be overwritten. It is an error to specify akey
when adding value to a data point, which is a list. -
flat
orflatten
will decompose the Yaml structure before adding to the data point in the original yaml. Adding values to aMap
then the top level of the Yaml structure to be added also has to beMap
. Adding values to aList
then the top level of the Yaml structure to be added also has to beList
. Adding aMap
this way the key value pairs of the map will be added to the original Yaml map. Adding aList
this way the values of the list will be appended to the original Yaml list. When this option is specified it is an error to specify anykey
since in this case the keys of the map will be used.
This example adds a new value to the root of the Yaml structure.
{@yaml:define a=
a: this is a simple Yaml with a top level Map
}
{@yaml:add to=a key=b
this is the value to be added to yaml structure a
}
{a}
will result:
a: this is a simple Yaml with a top level Map
b: this is the value to be added to yaml structure a
In this example the value is added to the value of the map from the top level named b
.
{@yaml:define a=
a: this is a simple Yaml with a top level Map
b: {}
}
{@yaml:add to=a.b key=c
this is the value to be added to yaml structure a
}
{a}
will result:
a: this is a simple Yaml with a top level Map
b:
c: this is the value to be added to yaml structure a
This example will add multiple elements to a map inside the yaml structure.
{@yaml:define docker=
version: "3.6"
services:
jamal-mongodb:
build:
args:
dump_dir: dump
context: ./config-dev/mongodb
container_name: zrch-mongodb
environment:
- TZ=Europe/Zurich
image: zrch/mongodb:1.0.1-dev
labels:
com.javax0.jamal.description: "Persistence service."
com.javax0.jamal.is-production: "false"
ports:
- "27017:27017"
}
{@yaml:add to=docker.services["jamal-mongodb"].labels flat
com.javax0.jamal.title: "Non-relational DB Instance"
com.javax0.jamal.sizing: 1000
com.javax0.jamal.nodeType: primary
}
{docker}
will result:
version: '3.6'
services:
jamal-mongodb:
build:
args:
dump_dir: dump
context: ./config-dev/mongodb
container_name: zrch-mongodb
environment:
- TZ=Europe/Zurich
image: zrch/mongodb:1.0.1-dev
labels:
com.javax0.jamal.description: Persistence service.
com.javax0.jamal.is-production: 'false'
com.javax0.jamal.title: Non-relational DB Instance
com.javax0.jamal.sizing: 1000
com.javax0.jamal.nodeType: primary
ports:
- 27017:27017
This example adds one element to an array. The added element itself is an array. It is not flattened
{@yaml:define a=
- this is a simple Yaml with a top level Map
- kukuruc
}
{@yaml:add to=a
- this is one element
- this is the second element}
{a}
will result:
- this is a simple Yaml with a top level Map
- kukuruc
- - this is one element
- this is the second element
If we use flattening, we get a different result
{@yaml:define a=
- this is a simple Yaml with a top level Map
- kukuruc
}
{@yaml:format flowStyle=BLOCK}
{@yaml:add to=a flatten
- this is one element
- this is the second element
}
{a}
will result:
- this is a simple Yaml with a top level Map
- kukuruc
- this is one element
- this is the second element
This macro can be used to set the options for Snake Yaml. The format of the macro is
{@yaml:format options}
The options of the macro are
-
allowUnicode
specify whether to emit non-ASCII printable Unicode characters. -
canonical
force the emitter to produce a canonical YAML document. -
explicitEnd
force to add…
at the end of the Yaml data -
explicitStart
force to ass---
at the start of the yaml data -
prettyFlow
instruct the output to follow pretty flow -
splitLines
instruct the output to split too long lines -
defaultFlowStyle
,flowStyle
the flow style can beFLOW
,BLOCK
orAUTO
-
defaultScalarStyle
,scalarStyle
the scalar style can beDOUBLE_QUOTED
,SINGLE_QUOTED
,LITERAL
,FOLDED
, orPLAIN
, -
lineBreak
the output line break can beWIN
,MAC
, orUNIX
-
indent
sets the indentation size, should be max 10 -
indicatorIndent
set the number of white-spaces to use for the sequence indicator '-' -
width
sets the desired width
Each of these options has a setXXX
counterpart in SnakeYaml DumperOption
class.
setXXX
methods with boolean argument need boolean option in this macro.
Similarly, methods with in argument need integer options.
Those options that have an enum setXXX
counterpart should use the name of the individual enum values.
In these examples, we will use the yaml structure:
{@yaml:define yaml=
a: this is a euuu
b: this is b
bb:
h:
z:
t: t34 panzer
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1,2,3]
}
{@yaml:format}{@comment this is resetting all previous formatting}
{yaml}
It prints as the following without specifying any format:
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
allowUnicode
instructs the snake yaml output to include the unicode characters into the output instead of using escape sequences in string.
{@yaml:format allowUnicode}
{yaml}
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
The option canonical
instructs snake yaml to output the structure in a canonical format.
{@yaml:format canonical}
{yaml}
---
!!map {
? !!str "a"
: !!str "this is a euuu",
? !!str "b"
: !!str "this is b",
? !!str "bb"
: !!map {
? !!str "h"
: !!map {
? !!str "z"
: !!map {
? !!str "t"
: !!str "t34 panzer",
},
},
},
? !!str "c"
: !!seq [
!!int "1",
!!int "2",
!!str "this is\n a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n",
],
? !!str "k"
: !!int "3",
? !!str "h"
: !!seq [
!!int "1",
!!int "2",
!!int "3",
],
}
The option explicitStart
and explicitEnd
instructs snake yaml to output the starting ---
and ending …
characters.
{@yaml:format explicitEnd explicitStart}
{yaml}
---
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
...
The option prettyFlow
instructs snake yaml to output the yaml structure in pretty flow.
{@yaml:format prettyFlow}
{yaml}
a: this is a euuu
b: this is b
bb:
h:
z: {
t: t34 panzer
}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [
1,
2,
3
]
The option splitLines
instructs snake yaml to output the yaml structure splitting the lines.
{@yaml:format splitLines}
{yaml}
For some reason, it does not split the lines, may be later versions of snakeYaml will do.
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
{@yaml:format flowStyle=FLOW}
FLOW
{yaml}
{@yaml:format flowStyle=BLOCK}
BLOCK
{yaml}
{@yaml:format flowStyle=AUTO}
AUTO
{yaml}
FLOW
{a: this is a euuu, b: this is b, bb: {h: {z: {t: t34 panzer}}}, c: [1, 2, "this is\n a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"], k: 3, h: [1, 2, 3]}
BLOCK
a: this is a euuu
b: this is b
bb:
h:
z:
t: t34 panzer
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h:
- 1
- 2
- 3
AUTO
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
{@yaml:format scalarStyle=DOUBLE_QUOTED}
DOUBLE_QUOTED
{yaml}
{@yaml:format scalarStyle=SINGLE_QUOTED}
SINGLE_QUOTED
{yaml}
{@yaml:format scalarStyle=LITERAL}
LITERAL
{yaml}
{@yaml:format scalarStyle=FOLDED}
FOLDED
{yaml}
{@yaml:format scalarStyle=PLAIN}
PLAIN
{yaml}
DOUBLE_QUOTED
"a": "this is a euuu"
"b": "this is b"
"bb":
"h":
"z":
"t": "t34 panzer"
"c":
- !!int "1"
- !!int "2"
- "this is\n a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"
"k": !!int "3"
"h":
- !!int "1"
- !!int "2"
- !!int "3"
SINGLE_QUOTED
'a': 'this is a euuu'
'b': 'this is b'
'bb':
'h':
'z':
't': 't34 panzer'
'c':
- !!int '1'
- !!int '2'
- "this is\n a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"
'k': !!int '3'
'h':
- !!int '1'
- !!int '2'
- !!int '3'
LITERAL
"a": |-
this is a euuu
"b": |-
this is b
"bb":
"h":
"z":
"t": |-
t34 panzer
"c":
- !!int |-
1
- !!int |-
2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
"k": !!int |-
3
"h":
- !!int |-
1
- !!int |-
2
- !!int |-
3
FOLDED
"a": >-
this is a euuu
"b": >-
this is b
"bb":
"h":
"z":
"t": >-
t34 panzer
"c":
- !!int >-
1
- !!int >-
2
- >
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
"k": !!int >-
3
"h":
- !!int >-
1
- !!int >-
2
- !!int >-
3
PLAIN
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
{@yaml:format indent=5}
{yaml}
a: this is a euuu
b: this is b
bb:
h:
z: {t: t34 panzer}
c:
- 1
- 2
- |
this is
a multi
line
string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
The macro yaml:dump
can dump the Yaml data structure to a file.
The format of the macro is
{@yaml:dump yamlMacro to file_name}
where yamlMacro
is the name of the macro that holds the Yaml data structure.
file_name
is the name of the file where the Yaml formatted content is to be written.
The to
separating them is a keyword to ease readability.
The following structure presents an example:
{@yaml:define x=[a,b,c]}
{@yaml:dump x to ./target/dump.yaml}
{@include [verbatim] ./target/dump.yaml}
will result
[a, b, c]
There is no reason to dump an unresolved structure into a file.
If the macro to be dumped to the file was not yet resolved, it will be resolved.
The resolution process will be in-place unless the option yamlResolveClone
(alias clone
) is used before the name of the macro between (
and )
characters.
{@yaml:dump (clone) x to ./target/dump.yaml}
The macro yaml:xml
converts a Yaml structure to XML format.
The format of the macro is:
{@yaml:xml (options) yamlMacroName}
Here yamlMacroName
is the name of a Yaml macro that was defined using yaml:define
.
Before converting, the Yaml structure will be resolved in case it was not resolved yet.
For this the options clone
and copy
can be specified.
For more information in these options see the macro documentation of yaml:resolve
.
In addition to these options you can use the options
-
yamlXmlTopTag
(aliastag
) can specify the name of the top level tag of the XML. The default value isxml
-
yamlXmlAttributes
(aliasattributes
) can specify extra attributes for the top level XML tag. The default value is not to specify any attribute for the top level tag.
The maps from the Yaml structure the (key,value) elements will be converted to the XML
<key>value</key>
structure.
Here value
can be another map, a list or something else represented as a string.
For example:
{@yaml:define z=
tagValues:
a: 1
b: 2
c: 3}
{#xmlFormat {@yaml:xml (tag="tag" attributes="a=\"53\"")z}}
will be converted to
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tag a="53">
<tagValues>
<a>1</a>
<b>2</b>
<c>3</c>
</tagValues>
</tag>
You can also specify attributes, use of CDATA and tag names for list elements using special classes.
Jamal contains five classes, which can be referred to in Yaml files, and they are treated special during the XML conversion.
When reading and writing Yaml files Jamal defines special tags for these classes.
These tags start with a single !
character and the name of the tag.
The tags are listed here.
-
!attr
will be used as an attribute for the tag containing this object. When the containing object is a map, the!attr
object can be a simple string, or it can be a map. When the!attr
is a simple string, the key of it will be used as attribute name. If the!attr
object is a member of an array then it can only be a map. When the!attr
object is a map then the keys and values will be used as attribute keys and values. You have to use this form in a map if the name of the attribute is the same as one of the content keys.
{@yaml:define z=
tagValues:
myTag: !attr my name
yourTag: !attr your name
cAttributeMustBeAMap: !attr {c: colliding tag name}
c:
- !attr {name: my name}
- !tag cc
- 1
- 2
}
{#xmlFormat {@yaml:xml (tag="tag" attributes="a=\"53\"")z}}
will be converted to
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tag a="53">
<tagValues c="colliding tag name" myTag="my name" yourTag="your name">
<c name="my name">
<cc>1</cc>
<cc>2</cc>
</c>
</tagValues>
</tag>
-
!text
will be used as the text value for the tag containing this object. In an ordinary situation this just happens when you specify a string. In those cases there is no need for this class. It is only needed when you specified an attribute for some object using an!attr
for an object, which is supposed to be a text. To use the!attr
you had to specify the object as a Map already containing a key with the attribute. When the conversion sees the!text
object it knows that it must treat this Map as a special one and should convert it to a simple tag with a text content. If you use this object type it has to be the last in the map. Any further keys will result error.
{@yaml:define z=
text_tag:
name: !attr Peter Muster
textContent: !text This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}
will result in:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
<text_tag name="Peter Muster">This is the text content</text_tag>
</myXml>
-
!cdatatext
is the same as theTEXT
, but it will also enclose the text as CDATA.
{@yaml:define z=
text_tag:
name: !attr Peter Muster
textContent: !cdatatext This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}
will result in:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
<text_tag name="Peter Muster"><![CDATA[This is the text content]]></text_tag>
</myXml>
-
!cdata
will instruct the converter to convert the actual node to CDATA.
{@yaml:define z=
text_tag:
name: !attr Peter Muster
textContent: !cdata This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}
will result in:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
<text_tag name="Peter Muster">
<textContent><![CDATA[This is the text content]]></textContent>
</text_tag>
</myXml>
-
!tag
can specify a TAG name for the list members. The default behaviour is that a list will be converted to a<As><A></A><A></A>…</As>
structure. HereAs
is the plural form of a word, likedependencies
. The conversion will calculate the singular in the very simple way chopping off the last character. In the example case it will bedependencie
, which is eventually wrong. To save the day a objectTAG
can be used. The value of this object will be used as the tag for the list elements. TheTAG
object can be interleaved with!attr
objects, but it should never be specified twice for the same list and it always should preceed the first "real" list member. You MUST use aTAG
if the enclosing object tag is a single character.You can find examples of the use of these classes in the file TestXml.jyt
{@yaml:define z=
tagValues:
- a
- b
- c}
{#xmlFormat {@yaml:xml z}}
will be converted to
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
<tagValues>
<tagValue>a</tagValue>
<tagValue>b</tagValue>
<tagValue>c</tagValue>
</tagValues>
</xml>
The tag names in the list is the same as the one containing the list with the last character chopped off. The convention is that map members that contain lists should be some plural nouns having an extra 's' at the end.
If a list element is a list itself then the iterated tag value will be the same as the enclosing one chopping off another character again. For example:
{@yaml:define z=
tagValues:
- a
- [x, y, z, k]
- c}
{#xmlFormat {@yaml:xml (tag=tagV)z}}
will be converted to
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tagV>
<tagValues>
<tagValue>a</tagValue>
<tagValue>
<tagValu>x</tagValu>
<tagValu>y</tagValu>
<tagValu>z</tagValu>
<tagValu>k</tagValu>
</tagValue>
<tagValue>c</tagValue>
</tagValues>
</tagV>
There is a limitation in the Yaml structure. Yaml structures can be recursive but XML cannot be. In case the Yaml structure is recursive or too deep (by default 300) then Jamal will stop the evaluation.
Note
|
This macro was introduced in Jamal version 1.7.5. The version had a major bug that rendered this macro unusable. Version 1.7.6 extended the conversion from Yaml to XML making it possible to use CDATA sections, spefify tag names for lists and to add attributes to tags. |
The macro yaml:output
redefines the output of the Jamal processing.
The format of the macro is:
{@yaml:output yamlMacro}
Here the yamlMacro
is the name of a YAML macro to be rendered as the final output of the Jamal processing.
It has to be defined at the end of the processing.
It also means that this macro has to be on the top level in the macro hierarchy.
In other words, it has to be a global macro.
When this macro is used, the output of the Jamal processing will be the Yaml formatted structure of the data held in the macro yamlMacro
.
If this macro contained references and was not yet resolved, then it will be resolved.
Since this is the last step to processing the whole Jamal structure following the entire process, usually there is no need for cloning.
If for any reason there is need for cloning then the clone
option may be used on the command.
The command also supports the copy
option.
There is no special macro to support looping over values in a YAML structure.
It can be done using the core for
macro.
The core for
macro has two forms.
The conventional is when the macro loops over strings provided in the macro invocation.
In this case the loop the in
keyword separates the variables from the value list.
The other case is when the looping is over some Java object.
In this case the from
keyword separates the variable from the macro holding the object.
These macros must be so-called "object holder" macros, and the user-defined YAML macros are such.
In the following example we have a YAML structure defined in the macro games
.
This structure has two lists, PCgames
and ConsoleGames
.
We want to loop over the PCgames
list.
{@yaml:define games=
PCgames:
- "Doom"
- "Quake"
- "Unreal"
ConsoleGames:
- "Mario"
- "Zelda"
- "Metroid"
}
{@yaml:set cGames=/games.PCgames}
{@for game from cGames=
- game}
The output is the list of the games under the PCgames
key.
- Doom
- Quake
- Unreal
Note that we had to use the macro yaml:set
to set the cGames
macro to the PCgames
list.
That is because the for
macro can only loop over an object provided by a macro.
Writing
{@yaml:define games=
PCgames:
- "Doom"
- "Quake"
- "Unreal"
ConsoleGames:
- "Mario"
- "Zelda"
- "Metroid"
}
{@yaml:set cGames=/games.PCgames}
{@try! {#for game from {@yaml:get /games.PCgames}
- game}}
will result in the error message:
The user defined macro '[Doom,' does not exist or cannot be used as data source for a 'for' loop.
because the {@yaml:get /games.PCgames}
macro evaluated is a string and not a macro.
As you can see from the error message, the result of the macro is the list of the game names.
You can iterate through lists and maps in the same way. When you iterate through maps you will get the keys:
{@yaml:define cGames=
first: "Doom"
second: "Quake"
third: "Unreal"
}
{@for game from cGames=
- game}
The output is the list of the games under the PCgames
key.
- first
- second
- third
If you want to go deeper, you can use the loop variable as a key in the map. For example, you can loop through the list of games by key:
cGames is still defined from the prior example
{!@for game from cGames=
{@yaml:get (from=cGames) game}}
The output is the list of the games under cGames
cGames is still defined from the prior example
Doom
Quake
Unreal
For a full-fledged example how to document an OpenAPI stack using Jamal and the YAML macros, visit the documentation at https://github.com/serverless-u/AxsessGard/blob/main/README.adoc.jam.