= Metanorma-plugin-datastruct == Functionality Metanorma plugin that allows you to access static data structures like JSON, YAML, XML from a Metanorma document == Installation [source,console] ---- $ gem install metanorma-plugin-datastruct ---- == Usage In order to use the macros in Metanorma, add the gem gem 'metanorma-plugin-datastruct' in your Gemfile. Or have the gem installed, and Metanorma can use them automatically via the extension :plugin: datastruct. === Expressions Currently, there are 2 plugins available: `yaml2text` and `json2text`. As states from the name, `yaml2text` allows to load yaml file and use it in expressions, `json2text` works with json files. Macroses supports all https://shopify.github.io/liquid/basics/introduction/[Liquid syntax expressions], including: * variables, variable assignment * flow control (if/case) * filters * loops See https://shopify.github.io/liquid/basics/introduction/[here] for the full description of Liquid tags and expressions. [[defining_syntax]] === Defining the block A `yaml2text`(`json2text`) block is created with the following syntax. Block opening and closing is demarcated by an open block syntax (`--`) or the `[source]` block syntax (`----` or more `-`). [source,adoc] -- [yaml2text,{YAML(JSON) file path},{self-defined context name}] ---- this is content within the block! ---- -- Where: * content within the block is called the "`template`"; * `{YAML(JSON) file path}` is the location of the YAML(JSON) file that contains data to be loaded. Location of the file is computed relative to the source directory that `[yaml2text]`(`[json2text]`) is used (e.g., if `[yaml2text,data.yaml,data]` is invoked in an `.adoc` file located at `/foo/bar/doc.adoc`, the data file is expected to be found at `/foo/bar/data.yaml`); * `{self-defined context name}` is the name where the data read from the data file can be accessed with. === Interpolation `yaml2text`(`json2text`) accepts string interpolation of the following forms: . `{variable}`: as in AsciiDoc syntax; . `{{ variable }}`, `{% if/else/for/case %}`: basic Liquid tags and expressions are supported. The value within the curly braces will be interpolated by `yaml2text`(`json2text`). Where: * In `{variable}`(`{{variable}}`), `variable` is the name of the variable or AsciiDoc attribute. * The location of `{variable}`(`{{variable}}`) in text will be replaced with the value of `variable`. * Evaluation order will be first from the defined context, then of the Metanorma AsciiDoc document. === Accessing object values Object values are accessed via the `.` (dot) separator. EXAMPLE: -- Given: strings.yaml [source,yaml] ---- --- foo: bar dead: beef ---- And the block: [source,asciidoc] ------ [yaml2text,strings.yaml,data] ---- I'm heading to the {{data.foo}} for {{data.dead}}. ---- ------ The file path is `strings.yaml`, and context name is `data`. `{{data.foo}}` evaluates to the value of the key `foo` in `data`. Will render as: [source,asciidoc] ---- I'm heading to the bar for beef. ---- -- === Accessing arrays ==== Length The length of an array can be obtained by `{{arrayname.size}}`. EXAMPLE: -- Given: strings.yaml [source,yaml] ---- --- - lorem - ipsum - dolor ---- And the block: [source,asciidoc] ------ [yaml2text,strings.yaml,data] ---- The length of the YAML array is {{data.size}}. ---- ------ The file path is `strings.yaml`, and context name is `data`. `{{data.size}}` evaluates to the length of the array using liquid `size` https://shopify.github.io/liquid/filters/size/[filter]. Will render as: [source,asciidoc] ---- The length of the YAML array is 3. ---- -- ==== Enumeration and context The following syntax is used to enumerate items within an array: [source,asciidoc] -- {% for item in array_name %} ...content... {% endfor %} -- Where: * `array_name` is the name of the existing context that contains array data * `item` is the current item within the array Within an array enumerator, the following https://shopify.dev/docs/themes/liquid/reference/objects/for-loops[expressions] can be used: * `{{forloop.index0}}` gives the zero-based position of the item `item_name` within the parent array * `{{forloop.length}}` returns the number of iterations of the loop. * `{{forloop.first}}` returns `true` if it's the first iteration of the for loop. Returns `false` if it is not the first iteration. * `{{forloop.last}}` returns `true` if it's the last iteration of the for loop. Returns `false` if it is not the last iteration. * `{{array_name.size}}` gives the length of the array `array_name` * `{{array_name[i]}}` provides the value at index `i` (zero-based: starts with `0`) in the array `array_name`; `-1` can be used to refer to the last item, `-2` the second last item, and so on. EXAMPLE: -- Given: strings.yaml [source,yaml] ---- --- - lorem - ipsum - dolor ---- And the block: [source,asciidoc] ------ [yaml2text,strings.yaml,arr] ---- {% for item in arr %} === {{forloop.index0}} {item} This section is about {item}. {endfor} ---- ------ Where: * file path is `strings.yaml` * current context within the enumerator is called `item` * `{{forloop.index0}}` gives the zero-based position of item `item` in the parent array `arr`. Will render as: [source,text] ---- === 0 lorem This section is about lorem. === 1 ipsum This section is about ipsum. === 2 dolor This section is about dolor. ---- -- === Accessing objects ==== Size Similar to arrays, the number of key-value pairs within an object can be obtained by `{{objectname.size}}`. EXAMPLE: -- Given: object.yaml [source,yaml] ---- --- name: Lorem ipsum desc: dolor sit amet ---- And the block: [source,asciidoc] ------ [yaml2text,object.yaml,data] ---- === {{data.name}} {{data.desc}} ---- ------ The file path is `object.yaml`, and context name is `data`. `{{data.size}}` evaluates to the size of the object. Will render as: [source,asciidoc] ---- === Lorem ipsum dolor sit amet ---- -- ==== Enumeration and context The following syntax is used to enumerate key-value pairs within an object: [source,asciidoc] -- {% for item in object_name %} {{item[0]}}, {{item[1]}} {% endfor %} -- Where: * `object_name` is the name of the existing context that contains the object * `{{item[0]}}` contains the key of the current enumrated object * `{{item[1]}}` contains the value * `{% endfor %}` indicates where the object enumeration block ends EXAMPLE: -- Given: object.yaml [source,yaml] ---- --- name: Lorem ipsum desc: dolor sit amet ---- And the block: [source,asciidoc] ------ [yaml2text,object.yaml,my_item] ---- {% for item in my_item %} === {{item[0]}} {{item[1]}} {% endfor %} ---- ------ Where: * file path is `object.yaml` * current key within the enumerator is called `item[0]` * `{{item[0]}}` gives the key name in the current iteration * `{{item[1]}}` gives the value in the current iteration Will render as: [source,text] ---- === name Lorem ipsum === desc dolor sit amet ---- -- Moreover, the `keys` and `values` attributes can also be used in object enumerators. EXAMPLE: -- Given: object.yaml [source,yaml] ---- --- name: Lorem ipsum desc: dolor sit amet ---- And the block: [source,asciidoc] ------ [yaml2text,object.yaml,item] ---- .{{item.values[1]}} [%noheader,cols="h,1"] |=== {% for elem in item %} | {{elem[0]}} | {{elem[1]}} {% endfor %} |=== ---- ------ Where: * file path is `object.yaml` * current key within the enumerator is called `key` * `{{item[1]}}` gives the value of key in the current iteration the parent array `my_item`. * `{{item.values[1]}}` gives the value located at the second key within `item` Will render as: [source,text] ---- .dolor sit amet [%noheader,cols="h,1"] |=== | name | Lorem ipsum | desc | dolor sit amet |=== ---- -- There are several optional arguments to the for tag that can influence which items you receive in your loop and what order they appear in: * limit: lets you restrict how many items you get. * offset: lets you start the collection with the nth item. * reversed iterates over the collection from last to first. EXAMPLE: -- Given: strings.yaml [source,yaml] ---- --- - lorem - ipsum - dolor - sit - amet ---- And the block: [source,asciidoc] ------ [yaml2text,strings.yaml,items] ---- {% for elem in items limit:2 offset:2 %} {{item}} {% endfor %} ---- ------ Where: * file path is `strings.yaml` * `limit` - how many items we shoudl take from the array * `offset` - zero-based offset of item from which start the loop * `{{item}}` gives the value of item in the array Will render as: [source,text] ---- dolor sit ---- -- == Advanced examples With the syntax of enumerating arrays and objects we can now try more powerful examples. === Array of objects EXAMPLE: -- Given: array_of_objects.yaml [source,yaml] ---- --- - name: Lorem desc: ipsum nums: [2] - name: dolor desc: sit nums: [] - name: amet desc: lorem nums: [2, 4, 6] ---- And the block: [source,asciidoc] ------ [yaml2text,array_of_objects.yaml,ar] ---- {% for item in ar %} {{item.name}}:: {{item.desc}} {% for num in item.nums %} - {{item.name}}: {{num}} {% endfor %} {% endfor %} ---- ------ Notice we are now defining multiple contexts: * using different context names: `ar`, `item`, and `num` Will render as: [source,asciidoc] ---- Lorem:: ipsum - Lorem: 2 dolor:: sit amet:: lorem - amet: 2 - amet: 4 - amet: 6 ---- -- === An array with interpolated file names (for AsciiDoc consumption) `yaml2text`(`json2text`) blocks can be used for pre-processing document elements for AsciiDoc consumption. EXAMPLE: -- Given: strings.yaml [source,yaml] ---- --- prefix: doc- items: - lorem - ipsum - dolor ---- And the block: [source,asciidoc] -------- [yaml2text,strings.yaml,yaml] ------ First item is {{yaml.items.first}}. Last item is {{yaml.items.last}}. {% for s in yaml.items %} === {{forloop.index0}} -> {{forloop.index0 | plus: 1}} {{s}} == {{yaml.items[forloop.index0]}} [source,ruby] ---- \include::{{yaml.prefix}}{{forloop.index0}}.rb[] ---- {% endfor %} ------ -------- Will render as: [source,asciidoc] ------ First item is lorem. Last item is dolor. === 0 -> 1 lorem == lorem [source,ruby] ---- \include::doc-0.rb[] ---- === 1 -> 2 ipsum == ipsum [source,ruby] ---- \include::doc-1.rb[] ---- === 2 -> 3 dolor == dolor [source,ruby] ---- \include::doc-2.rb[] ---- ------ --