asciidoc-parser 0.19.0

Parser for AsciiDoc format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
use crate::{blocks::Block, tests::prelude::*};
track_file!("ref/asciidoc-lang/docs/modules/pass/pages/pass-macro.adoc");

non_normative!(
    r#"
= Inline Passthroughs

AsciiDoc supports several inline passthrough macros and shorthands.
Inline passthroughs are designed to prevent subsitutions for regions of text, or to give you more fine-grained control over which substitutions are applied.

WARNING: Due to the fact that inline syntax in AsciiDoc is processed using substitutions rather than a descending grammar, it's possible to create invalid interleaving of inline elements, or other adverse interactions, that leads to invalid or illogical output.
The inline passthrough provides a bailout option to mitigate these entanglements.
This problem is expected to be resolved properly by the AsciiDoc Language Specification, which will mandate that inline syntax is parsed as a tree rather than through substitutions (to the degree possible).

"#
);

#[test]
fn inline_passthrough_macros() {
    verifies!(
        r#"
== Inline passthrough macros

[[def-plus]]single and double plus:: A special syntax for preventing inline text from being formatted.
Only xref:subs:special-characters.adoc[special characters] are replaced in the output format.
The substitutions can't be modified for this type of passthrough.

triple plus:: A special inline syntax for designating passthrough content.
No substitutions are applied nor can they be added using the step and group substitution values.

inline pass macro:: An inline macro named `pass` that can be used to passthrough content.
You can apply specific substitutions to the macro's target using substitution types and groups.
+
[source]
----
pass:[content like #{variable} passed directly to the output] followed by normal content.

content with only select substitutions applied: pass:c,a[__<{email}>__]
----

TIP: When you need to prevent or control the substitutions on one or more blocks of content, use a xref:pass-block.adoc[delimited passthrough block or the pass block style].

"#
    );

    let doc = Parser::default().parse(":email: foo@example.com\n\npass:[content like #{variable} passed directly to the output] followed by normal content.\n\ncontent with only select substitutions applied: pass:c,a[__<{email}>__]");

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "content like #{variable} passed directly to the output followed by normal content."
    );
}

#[test]
fn single_double_plus_1() {
    verifies!(
        r#"
[#single-double-plus]
== Single and double plus

The single and double plus passthroughs prevent text enclosed in either a pair of single pluses (`{plus}`) or a pair of double pluses (`{pp}`) from being formatted.

----
A +word+, a +sequence of words+, or ++char++acters that are escaped from formatting.
----

The single and double pluses represent the constrained and unconstrained passthrough, respectively.
They have boundaries that match the xref:text:index.adoc#formatting-marks-and-pairs[constrained and unconstrained formatting marks].
The main difference, however, is that they are applied first to suppress formatting.

"#
    );

    let doc = Parser::default().parse(
        "A +word+, a +sequence of words+, or ++char++acters that are escaped from formatting.",
    );

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "A word, a sequence of words, or characters that are escaped from formatting."
    );
}

#[test]
fn single_double_plus_2() {
    verifies!(
        r#"
This type of passthrough is intended to suppress any special meaning of the source text itself.
This passthrough type still ensures, however, that the content is properly escaped in the output.
That means the xref:subs:special-characters.adoc[special characters] substitution is still applied.

As with all constrained pairs, the single plus passthrough is designed to be used around a word or phrase.

----
A word or phrase between single pluses, such as +/document/{id}+, is not substituted.
However, the special characters +<+ and +>+ are still escaped in the output.

You can also escape formatting marks, like +``+.
----

"#
    );

    let doc = Parser::default().parse("A word or phrase between single pluses, such as +/document/{id}+, is not substituted.\nHowever, the special characters +<+ and +>+ are still escaped in the output.\n\nYou can also escape formatting marks, like +``+.");

    let mut blocks = doc.nested_blocks();

    let block1 = blocks.next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "A word or phrase between single pluses, such as /document/{id}, is not substituted.\nHowever, the special characters &lt; and &gt; are still escaped in the output."
    );

    let block2 = blocks.next().unwrap();

    let Block::Simple(sb2) = block2 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb2.content().rendered(),
        "You can also escape formatting marks, like ``."
    );
}

#[test]
fn single_double_plus_3() {
    verifies!(
        r#"
Being an unconstrained pair, the double plus passthrough can be used anywhere in the text.

----
Text formatting is not applied to a link target if it is surrounded by double pluses.
For example, link:++https://example.org/now_this__link_works.html++[].

You can also escape formatting marks, like all-natural++*++.

An attribute reference within a word, such as dev++{conf}++, is not replaced.
----

The single and plus passthroughs are a surefire alternative to backslash escaping.

Note that the single and plus passthroughs only prevent substitutions.
They do not format the text in monospace.
If you want to do both, you must enclose the pair in a monospace formatting pair, known as xref:text:literal-monospace.adoc[literal monospace].

"#
    );

    let doc = Parser::default().parse(":conf: /prod\n\nText formatting is not applied to a link target if it is surrounded by double pluses.\nFor example, link:++https://example.org/now_this__link_works.html++[].\n\nYou can also escape formatting marks, like all-natural++*++.\n\nAn attribute reference within a word, such as dev++{conf}++, is not replaced.");

    let mut blocks = doc.nested_blocks();

    let block1 = blocks.next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "Text formatting is not applied to a link target if it is surrounded by double pluses.\nFor example, <a href=\"https://example.org/now_this__link_works.html\" class=\"bare\">https://example.org/now_this__link_works.html</a>."
    );

    let block2 = blocks.next().unwrap();

    let Block::Simple(sb2) = block2 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb2.content().rendered(),
        "You can also escape formatting marks, like all-natural*."
    );

    let block3 = blocks.next().unwrap();

    let Block::Simple(sb3) = block3 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb3.content().rendered(),
        "An attribute reference within a word, such as dev{conf}, is not replaced."
    );
}

#[test]
fn triple_plus() {
    verifies!(
        r#"
[#triple-plus]
== Triple plus

The triple plus passthrough excludes content enclosed in a pair of triple pluses (pass:[+++]) from all substitutions.

 +++content passed directly to the output+++ followed by normal content.

The triple plus macro is often used to output custom HTML or XML.

[source]
----
include::example$pass.adoc[tag=3p]
----

====
include::example$pass.adoc[tag=3p]
====

"#
    );

    let doc =
        Parser::default().parse("The text +++<del>strike this</del>+++ is marked as deleted.");

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "The text <del>strike this</del> is marked as deleted."
    );
}

#[test]
fn inline_pass_macro() {
    verifies!(
        r#"
[#inline-pass]
== Inline pass macro

Like other inline passthroughs, the inline pass macro can be used to control the substitutions applied to a run of text.
To exclude inline content from all of the substitutions, enclose it in the inline pass macro.

Here's one way to format text as underline when generating HTML from AsciiDoc:

[source]
----
include::example$pass.adoc[tag=in-macro]
----

And here's the result.

====
include::example$pass.adoc[tag=in-macro]
====

WARNING: Using passthroughs to send content directly to the output can couple your content to a specific output format, such as HTML.
To avoid this risk, you should consider using conditional preprocessor directives to select content for different output formats based on the current backend.

What sets the inline pass macro apart from the alternatives is that it allows the substitutions to be customized.
The inline pass macro also plays a critical role in the document header.
In fact, it's the only macro that is processed in the document header by default as part of the xref:subs:index.adoc#header-group[header substitution group] (though it can be used to enable other substitutions, as demonstrated in this section).

Let's look at how to use the inline pass macro to hand select substitutions.

"#
    );

    let doc = Parser::default().parse(
        "The text pass:[<del>strike this</del>] is marked as deleted.
",
    );

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "The text <del>strike this</del> is marked as deleted."
    );
}

#[test]
fn custom_substitutions_1() {
    verifies!(
        r#"
=== Custom substitutions

You can customize the substitutions applied to the content of an inline pass macro by specifying one or more substitution values in the target of the macro.
Multiple values must be separated by commas and may not contain any spaces.
The substitution value is either the formal name of a substitution type or group, or its shorthand.

The following table lists the allowable substitution values:

.Substitution values accepted by the inline pass macro
[cols="1m,3m",width=50%]
|===
|Shorthand | Substitution Type

|c
|specialchars

|q
|quotes

|a
|attributes

|r
|replacements

|m
|macros

|p
|post replacements

h|Shorthand
h| Substitution Group

|n
|normal

|v
|verbatim
|===

For example, the quotes substitution (i.e., `q` or `quotes`) is enabled on the inline passthrough macro as follows:

[source]
----
include::example$pass.adoc[tag=in-macro-sub]
----

Here's the result.

====
include::example$pass.adoc[tag=in-macro-sub]
====

"#
    );

    let doc =
        Parser::default().parse("The text pass:q[<del>strike *this*</del>] is marked as deleted.");

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "The text <del>strike <strong>this</strong></del> is marked as deleted."
    );
}

#[test]
fn custom_substitutions_2() {
    verifies!(
        r#"
To enable multiple substitution groups, separate each value in the macro target by a comma:

[source]
----
include::example$pass.adoc[tag=in-macro-subs]
----

Here's the result.

====
include::example$pass.adoc[tag=in-macro-subs]
====

"#
    );

    let doc = Parser::default().parse(":docname: untitled document 1\n\nThe text pass:q,a[<del>strike _{docname}_</del>] is marked as deleted.");

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::Simple(sb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        sb1.content().rendered(),
        "The text <del>strike <em>untitled document 1</em></del> is marked as deleted."
    );
}

#[test]
fn nesting_blocks_and_paragraphs() {
    verifies!(
        r#"
== Nesting blocks and passthroughs

When you're using passthroughs inside literal and listing blocks, it can be easy to forget that the single plus and triple plus passthroughs are xref:subs:macros.adoc[macros substitutions].
To enable the passthroughs, assign the `macros` value to the `subs` attribute.

....
[source,java,subs="+quotes,+macros"]
----
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            **.antMatchers("/resources/+++**+++").permitAll()**
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .permitAll();
----
....

To learn more about applying substitutions to blocks, see xref:subs:apply-subs-to-blocks.adoc[].

[source,java,subs="+quotes,+macros"]
----
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            **.antMatchers("/resources/+++**+++").permitAll()**
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .permitAll();
----
"#
    );

    let doc = Parser::default().parse("[source,java,subs=\"+quotes,+macros\"]\n----\nprotected void configure(HttpSecurity http) throws Exception {\n    http\n        .authorizeRequests()\n            **.antMatchers(\"/resources/+++**+++\").permitAll()**\n            .anyRequest().authenticated()\n            .and()\n        .formLogin()\n            .loginPage(\"/login\")\n            .permitAll();\n----");

    let block1 = doc.nested_blocks().next().unwrap();

    let Block::RawDelimited(rdb1) = block1 else {
        panic!("Unexpected block type: {block1:?}");
    };

    assert_eq!(
        rdb1.content().rendered(),
        "protected void configure(HttpSecurity http) throws Exception {\n    http\n        .authorizeRequests()\n            <strong>.antMatchers(\"/resources/**\").permitAll()</strong>\n            .anyRequest().authenticated()\n            .and()\n        .formLogin()\n            .loginPage(\"/login\")\n            .permitAll();"
    );
}