use crate::tests::prelude::*;
track_file!("ref/asciidoc-lang/docs/modules/attributes/pages/counters.adoc");
fn rendered_blocks(input: &str) -> Vec<String> {
let doc = Parser::default().parse(input);
doc.nested_blocks()
.filter_map(|b| b.rendered_content().map(str::to_string))
.collect()
}
non_normative!(
r#"
= Counters
// document attributes and counters are NOT the same thing, but modifying a document attribute with the same name as the counter modifies the counter at the same time.
Counters are used to store and display ad-hoc sequences of numbers or Latin characters.
WARNING: Counters are a poorly defined feature in AsciiDoc and should be avoided if possible.
If you do use counters, you should only used them for the most rudimentary use cases, such as making a sequence in a list, table column, or prose.
You should *not* use counters to build IDs (i.e., references) or reference text.
Using counters across the boundaries of a reference will very likely result in unexpected behavior.
"#
);
#[test]
fn declare_and_display_a_counter() {
verifies!(
r#"
A counter is implemented as a specialized document attribute.
You declare and display a counter using an attribute reference, where the attribute name is prefixed with `counter:` (e.g., `+{counter:name}+`).
Since counters are attributes, counter names follow the same rules as xref:names-and-values.adoc#user-defined[attribute names].
The most important rule to note is that letters in counter names _must be lowercase_.
The counter value is incremented and displayed every time the `counter:` attribute reference is resolved.
The term [.term]*increment* means to advance the attribute value to the next value in the sequence.
If the counter value is an integer, add 1.
If the counter value is a character, move to the next letter in the Latin alphabet (e.g., a -> b).
The default start value of a counter is 1.
To create a sequence starting at 1, use the simple form `+{counter:name}+` as shown here:
[source]
The salad calls for {counter:seq1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears.
Here's the resulting output:
====
:!seq1:
The salad calls for {counter:seq1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears.
====
"#
);
assert_eq!(
rendered_blocks(
":!seq1:\nThe salad calls for {counter:seq1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears."
),
vec!["The salad calls for 1) apples, 2) oranges and 3) pears.".to_string()]
);
}
#[test]
fn use_a_counter_in_a_section_title() {
verifies!(
r#"
If you want to use a counter value in a section title, you should define it first using an attribute reference.
----
:seq1: {counter:seq1}
== Section {seq1}
The sequence in this section is {seq1}.
:seq1: {counter:seq1}
== Section {seq1}
The sequence in this section is {seq1}.
----
Here's the resulting output:
====
:!seq1:
:seq1: {counter:seq1}
[discrete]
== Section {seq1}
The sequence in this section is {seq1}.
:seq1: {counter:seq1}
[discrete]
== Section {seq1}
The sequence in this section is {seq1}.
====
"#
);
let doc = Parser::default().parse(
":!seq1:\n\n:seq1: {counter:seq1}\n[discrete]\n== Section {seq1}\n\nThe sequence in this section is {seq1}.\n\n:seq1: {counter:seq1}\n[discrete]\n== Section {seq1}\n\nThe sequence in this section is {seq1}.",
);
assert_xpath(&doc, "//h2[text()=\"Section 1\"]", 1);
assert_xpath(&doc, "//h2[text()=\"Section 2\"]", 1);
assert_output_contains(&doc, "The sequence in this section is 1.");
assert_output_contains(&doc, "The sequence in this section is 2.");
}
#[test]
fn counter2_increments_without_displaying() {
verifies!(
r#"
To increment the counter without displaying it (i.e., to skip an item in the sequence), use the `counter2` prefix instead:
[source]
{counter2:seq1}
WARNING: A `counter2` attribute reference on a line by itself will produce an empty paragraph.
You'll need to adjoin it to the nearest content to avoid this side effect.
"#
);
assert_eq!(
rendered_blocks("{counter2:seq1}\n\n{seq1}"),
vec![String::new(), "1".to_string()]
);
}
#[test]
fn display_the_current_value_without_incrementing() {
verifies!(
r#"
To display the current value of the counter without incrementing it, reference the counter name as you would any other attribute:
[source]
{counter2:pnum}This is paragraph {pnum}.
"#
);
assert_eq!(
rendered_blocks("{counter2:pnum}This is paragraph {pnum}."),
vec!["This is paragraph 1.".to_string()]
);
}
#[test]
fn create_a_character_sequence_or_custom_start_value() {
verifies!(
r#"
To create a character sequence, or start a number sequence with a value other than 1, specify a start value by appending it to the first use of the counter:
[source]
Dessert calls for {counter:seq1:A}) mangoes, {counter:seq1}) grapes and {counter:seq1}) cherries.
CAUTION: Character sequences either run from a,b,c,...x,y,z,{,|... or A,B,C,...,X,Y,Z,[,... depending on the start value.
Therefore, they aren't really useful for more than 26 items.
"#
);
assert_eq!(
rendered_blocks(
"Dessert calls for {counter:seq1:A}) mangoes, {counter:seq1}) grapes and {counter:seq1}) cherries."
),
vec!["Dessert calls for A) mangoes, B) grapes and C) cherries.".to_string()]
);
}
#[test]
fn reset_a_counter_by_unsetting_the_attribute() {
verifies!(
r#"
The start value of a counter is only recognized if the counter is _unset_ at that point in the document.
Otherwise, the start value is ignored.
To reset a counter attribute, unset the corresponding attribute using an attribute entry.
The attribute entry must be adjacent to a block or else it is ignored.
[source]
----
The salad calls for {counter:seq1:1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears.
:!seq1:
Dessert calls for {counter:seq1:A}) mangoes, {counter:seq1}) grapes and {counter:seq1}) cherries.
----
This gives:
====
:!seq1:
The salad calls for {counter:seq1:1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears.
:!seq1:
Dessert calls for {counter:seq1:A}) mangoes, {counter:seq1}) grapes and {counter:seq1}) cherries.
====
"#
);
assert_eq!(
rendered_blocks(
":!seq1:\nThe salad calls for {counter:seq1:1}) apples, {counter:seq1}) oranges and {counter:seq1}) pears.\n\n:!seq1:\nDessert calls for {counter:seq1:A}) mangoes, {counter:seq1}) grapes and {counter:seq1}) cherries."
),
vec![
"The salad calls for 1) apples, 2) oranges and 3) pears.".to_string(),
"Dessert calls for A) mangoes, B) grapes and C) cherries.".to_string(),
]
);
assert_eq!(
rendered_blocks("{counter:seq1:5} {counter:seq1:9} {counter:seq1:9}"),
vec!["5 6 7".to_string()]
);
}
#[test]
fn use_a_counter_for_part_numbers_in_a_table() {
verifies!(
r#"
Here's a full example that shows how to use a counter for part numbers in a table.
[source]
----
include::example$counter.adoc[tag=base]
----
Here's the output of that table:
====
include::example$counter.adoc[tag=base]
====
"#
);
let doc = Parser::default().parse(
".Parts{counter2:index:0}\n|===\n|Part Id |Description\n\n|PX-{counter:index}\n|Description of PX-{index}\n\n|PX-{counter:index}\n|Description of PX-{index}\n|===",
);
assert_xpath(&doc, "//caption[text()=\"Table 1. Parts\"]", 1);
assert_xpath(&doc, "//p[text()=\"PX-1\"]", 1);
assert_xpath(&doc, "//p[text()=\"Description of PX-1\"]", 1);
assert_xpath(&doc, "//p[text()=\"PX-2\"]", 1);
assert_xpath(&doc, "//p[text()=\"Description of PX-2\"]", 1);
}