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
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
use crate::tests::prelude::{inline_file_handler::InlineFileHandler, *};

track_file!("ref/asciidoc-lang/docs/modules/directives/pages/include.adoc");

non_normative!(
    r#"
= Includes
// Include Directive Syntax and Processing
// Include Directive Concepts and Syntax

"#
);

#[test]
fn intro() {
    verifies!(
        r#"
You can include content from another file into the current AsciiDoc document using the include directive.
The included content can be AsciiDoc or it can be any other text format.
Where that content is included in the document determines how it will be processed.

"#
    );

    let handler = InlineFileHandler::from_pairs([("partial.adoc", "Included paragraph.")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("Before include.\n\ninclude::partial.adoc[]\n\nAfter include.");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(
        paras,
        vec!["Before include.", "Included paragraph.", "After include."]
    );
}

#[test]
fn what_is_an_include_directive() {
    verifies!(
        r#"
== What is an include directive?

An [.term]*include directive* imports content from a separate file or URL into the content of the current document.
When the current document is processed, the include directive syntax is replaced by the contents of the include file.
Think of the include directive like a file expander.
The include directive is a <<include-processing,preprocessor directive>>, which means it has no awareness of the surrounding context.

"#
    );

    // The include directive is replaced by the contents of the include file
    // even when it appears inside a verbatim listing block, because the
    // preprocessor has no awareness of the surrounding context.
    let handler = InlineFileHandler::from_pairs([("expander.adoc", "EXPANDED")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("----\ninclude::expander.adoc[]\n----");

    let block = doc.nested_blocks().next().unwrap();
    assert_eq!(block.span().data(), "----\nEXPANDED\n----");
}

non_normative!(
    r#"
== When is an include directive useful?

The include directive is useful when you want to:

* Partition a large document into smaller files for better organization and to make restructuring simpler.footnote:[Always separate consecutive include directives by an empty line unless your intent is to adjoin the content in the include files so it becomes contiguous.]
* Insert source code from the external files where the code is maintained.
* Populate tables with output, such as CSV data, from other programs.
* Create document variants by combining the include directive with xref:conditionals.adoc[conditional preprocessor directives].
* Reuse content snippets and boilerplate content, such as term definitions, disclaimers, etc., multiple times within the same document.
* Define a common set of attributes across multiple documents (typically included into the document header).

"#
);

#[test]
fn must_be_on_a_line_by_itself() {
    verifies!(
        r#"
[#include-syntax]
== Include directive syntax

An include directive must be placed on a line by itself with the following syntax:

"#
    );

    // Because an include directive must be on a line by itself, a directive with
    // trailing content on the same line is not processed as an include.
    let handler =
        InlineFileHandler::from_pairs([("part1.adoc", "First"), ("part2.adoc", "Second")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::part1.adoc[] include::part2.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["include::part1.adoc[] include::part2.adoc[]"]);
}

non_normative!(
    r#"
[listing,subs=+quotes]
----
\include::target[leveloffset=__offset__,lines=__ranges__,tag(s)=__name(s)__,indent=__depth__,encoding=__encoding__,opts=optional]
----

"#
);

#[test]
fn target_is_required() {
    verifies!(
        r#"
The target is required.
"#
    );

    // An include directive with an empty target is not a valid directive, so it
    // is left untouched rather than processed or reported as unresolved. This
    // matches Asciidoctor, which renders `include::[]` literally.
    let handler = InlineFileHandler::from_pairs([("", "SHOULD NOT APPEAR")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_primary_file_name("main.adoc")
        .with_include_file_handler(handler)
        .parse("Before.\n\ninclude::[]\n\nAfter.");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Before.", "include::[]", "After."]);
}

non_normative!(
    r#"
The target may be an absolute path, a path relative to the current document, or a URL.
"#
);

#[test]
fn target_may_contain_spaces() {
    verifies!(
        r#"
Since the include directive is a line-oriented expression, the target may contain space characters.
However, the target must not start with a space character (since that would turn it into a description list term).
"#
    );

    // A target with internal space characters is resolved.
    let handler = InlineFileHandler::from_pairs([("my file.adoc", "Spaced target.")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::my file.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Spaced target."]);

    // A target that starts with a space is not treated as an include directive.
    let handler2 = InlineFileHandler::from_pairs([(" spaced.adoc", "SHOULD NOT APPEAR")]);

    let doc2 = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler2)
        .parse("include:: spaced.adoc[]");

    assert!(
        !rendered_paragraphs(&doc2)
            .iter()
            .any(|p| p.contains("SHOULD NOT APPEAR"))
    );
}

non_normative!(
    r#"
An absolute or relative path outside the directory of the outermost document will only be honored if the safe mode is unsafe.
A URL target will only be resolved if the security settings on the processor allows it (e.g., `allow-uri-read`).
See xref:include-uri.adoc[].

The leveloffset, lines, tag(s), indent, encoding, and opts attributes are optional, thus reducing the simplest case to the following:

"#
);

#[test]
fn simplest_case() {
    verifies!(
        r#"
----
\include::partial.adoc[]
----

"#
    );

    let handler = InlineFileHandler::from_pairs([("partial.adoc", "Simplest form.")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::partial.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Simplest form."]);
}

non_normative!(
    r#"
Specifying the encoding is essential if the include file is not encoded in UTF-8.
The value of this attribute must be an encoding recognized by Ruby (e.g., utf-8, iso-8859-1, windows-1252, etc), case insenstive.
If the include file is already encoded in UTF-8 (or contains a BOM), this attribute is unnecessary.

"#
);

#[test]
fn consecutive_includes_separated_by_blank_line() {
    verifies!(
        r#"
When using consecutive include directives, you should always separate them by an empty line unless your intention is to adjoin the content in the include files so it becomes contiguous.

For example, if you're using the include directive to include individual chapters, the include directives should be offset from each other by an empty line.
This strategy avoids relying on empty lines imported from the include file to keep the chapters separated.
That separation should be encoded in the parent document instead.

----
\include::chapter01.adoc[]

\include::chapter02.adoc[]

\include::chapter03.adoc[]
----

"#
    );

    // Separated by empty lines, the chapters remain distinct blocks.
    let handler = InlineFileHandler::from_pairs([
        ("chapter01.adoc", "Chapter one."),
        ("chapter02.adoc", "Chapter two."),
        ("chapter03.adoc", "Chapter three."),
    ]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse(
            "include::chapter01.adoc[]\n\ninclude::chapter02.adoc[]\n\ninclude::chapter03.adoc[]",
        );

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(
        paras,
        vec!["Chapter one.", "Chapter two.", "Chapter three."]
    );
}

#[test]
fn consecutive_includes_adjoined() {
    verifies!(
        r#"
On the other hand, if you're using the include directive to lay down contiguous lines, such as common document attribute entries, then you would put the include directives on adjacent lines to avoid inserting empty lines.

----
= Document Title
Author Name
\include::attributes-settings.adoc[]
\include::attributes-urls.adoc[]
:url-example: https://example.org

Document body.
----

"#
    );

    // Adjacent include directives adjoin the imported lines, so the attribute
    // entries remain contiguous in the document header.
    let handler = InlineFileHandler::from_pairs([
        ("attributes-settings.adoc", ":settings-attr: yes"),
        ("attributes-urls.adoc", ":url-attr: https://example.com"),
    ]);

    let doc = Parser::default().with_safe_mode(SafeMode::Server).with_include_file_handler(handler).parse(
        "= Document Title\nAuthor Name\ninclude::attributes-settings.adoc[]\ninclude::attributes-urls.adoc[]\n:url-example: https://example.org\n\nDocument body.",
    );

    let names: Vec<&str> = doc.header().attributes().map(|a| a.name().data()).collect();

    assert_eq!(names, vec!["settings-attr", "url-attr", "url-example"]);
}

non_normative!(
    r#"
In either case, don't rely on the empty lines at the boundaries of the include file.
And mind where empty lines are used in that include file.

[#include-processing]
== Include processing

Although the include directive looks like a block macro, *it's not a macro and therefore isn't processed like one*.
It's a preprocessor directive; it's important to understand the distinction.

include::partial$preprocessor.adoc[]
The include directive is a preprocessor directive that always adds lines.

"#
);

#[test]
fn replaced_by_lines_then_interpreted() {
    verifies!(
        r#"
The best way to think of the include directive is to imagine that it is being replaced by the lines from the include file (i.e., the imported lines).
Only after the lines from the target of the include directive are added to the current document does the parser read and interpret those lines.

"#
    );

    // After the imported lines are added, the parser reads and interprets them:
    // an included section heading becomes a real section block.
    let handler =
        InlineFileHandler::from_pairs([("sec.adoc", "== Included Section\n\nSection body.")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("= Doc Title\n\ninclude::sec.adoc[]");

    let block = doc.nested_blocks().next().unwrap();
    assert!(matches!(block, crate::blocks::Block::Section(_)));
}

#[test]
fn disabled_in_secure_mode() {
    verifies!(
        r#"
IMPORTANT: The include directive is disabled when Asciidoctor is run in secure mode.
In secure mode, the include directive is converted to a link in the output document.
See xref:asciidoctor::safe-modes.adoc[] to learn more.

"#
    );

    // At `SafeMode::Secure` (the default), the include directive is not
    // processed. Even though a handler is configured that would resolve the
    // target, the directive is converted to a link (carrying the `include`
    // role) so the file's contents are never embedded.
    let handler = InlineFileHandler::from_pairs([("inc.adoc", "SHOULD NOT APPEAR")]);
    let doc = Parser::default()
        .with_include_file_handler(handler)
        .parse("= Doc Title\n\ninclude::inc.adoc[]");

    assert_eq!(
        rendered_paragraphs(&doc),
        vec![r#"<a href="inc.adoc" class="bare include">inc.adoc</a>"#.to_string()]
    );
}

#[test]
fn escaping_an_include_directive() {
    verifies!(
        r#"
== Escaping an include directive

If you don't want the include directive to be processed, you must escape it using a backslash.

"#
    );

    // An escaped include directive is not processed: its target is not resolved.
    // Matching Asciidoctor, the leading backslash is stripped and the remainder
    // is emitted literally.
    let handler = InlineFileHandler::from_pairs([("just-an-example.ext", "SHOULD NOT APPEAR")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("\\include::just-an-example.ext[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["include::just-an-example.ext[]"]);
}

#[test]
fn indentation_prevents_processing() {
    verifies!(
        r#"
// NOTE: the following listing uses indentation to prevent the directive from being processed
[indent=0]
----
 \include::just-an-example.ext[]
----

"#
    );

    // The indented directive from the spec example does not cause an inclusion:
    // the listing block retains the directive text verbatim.
    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(InlineFileHandler::from_pairs([(
            "just-an-example.ext",
            "SHOULD NOT APPEAR",
        )]))
        .parse("[indent=0]\n----\n \\include::just-an-example.ext[]\n----");

    let block = doc.nested_blocks().next().unwrap();
    assert_eq!(
        block.span().data(),
        "[indent=0]\n----\n \\include::just-an-example.ext[]\n----"
    );

    // It is the leading whitespace (indentation), not the backslash, that
    // prevents processing: an indented directive without a backslash is left
    // untouched too.
    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(InlineFileHandler::from_pairs([(
            "just-an-example.ext",
            "SHOULD NOT APPEAR",
        )]))
        .parse("[indent=0]\n----\n include::just-an-example.ext[]\n----");

    let block = doc.nested_blocks().next().unwrap();
    assert_eq!(
        block.span().data(),
        "[indent=0]\n----\n include::just-an-example.ext[]\n----"
    );
}

#[test]
fn escaping_required_even_in_verbatim_block() {
    verifies!(
        r#"
Escaping the directive is necessary _even if it appears in a verbatim block_ since it's not aware of the surrounding document structure.

"#
    );

    // An unescaped include is processed even inside a verbatim block, so escaping
    // is required there to prevent it from being expanded.
    let handler = InlineFileHandler::from_pairs([("ex.adoc", "IMPORTED")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("----\ninclude::ex.adoc[]\n----");

    let block = doc.nested_blocks().next().unwrap();
    assert_eq!(block.span().data(), "----\nIMPORTED\n----");
}

non_normative!(
    r#"
[#include-resolution]
== Include file resolution

The path used in an include directive can be relative or absolute.

If the path is relative, the processor resolves the path using the following rules:

* If the include directive is used in the primary (top-level) document, relative paths are resolved relative to the base directory.
(The base directory defaults to the directory of the primary document and can be overridden from the CLI or API).
* If the include directive is used in a file that has itself been included, the path is resolved relative to the including (i.e., current) file.

//TODO show examples to contrast a relative vs an absolute include

These defaults make it easy to reason about how the path to the include file is resolved.

If the processor cannot locate the file (perhaps because you mistyped the path), you'll still be able to convert the document.
However, you'll get the following warning message during conversion:

 asciidoctor: WARNING: my-document.adoc: line 3: include file not found: /.../content.adoc

"#
);

#[test]
fn unresolved_directive_inserted_into_output() {
    verifies!(
        r#"
The following message will also be inserted into the output:

 Unresolved directive in my-document.adoc - include::content.adoc[]

"#
    );

    // When the target cannot be resolved, the directive is replaced in the output
    // with an "Unresolved directive" message.
    let handler = InlineFileHandler::from_pairs([("other.adoc", "unused")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_primary_file_name("my-document.adoc")
        .with_include_file_handler(handler)
        .parse("Before.\n\ninclude::content.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(
        paras,
        vec![
            "Before.",
            "Unresolved directive in my-document.adoc - include::content.adoc[]"
        ]
    );

    // A warning is also emitted via the crate's warning mechanism, pointing at
    // the inserted message.
    let warnings: Vec<_> = doc.warnings().collect();
    assert_eq!(warnings.len(), 1);
    assert_eq!(
        warnings[0].warning,
        WarningType::IncludeFileNotFound("content.adoc".to_owned())
    );
    assert_eq!(
        warnings[0].source.data(),
        "Unresolved directive in my-document.adoc - include::content.adoc[]"
    );
}

non_normative!(
    r#"
To fix the problem, edit the file path and run the converter again.
"#
);

#[test]
fn opts_optional_drops_unresolved_include() {
    verifies!(
        r#"
If you don't want the AsciiDoc processor to emit a warning, but rather drop the include that cannot be found, add the `opts=optional` attribute to the include directive.

"#
    );

    // The handler does not provide content.adoc, but `opts=optional` means the
    // unresolved directive is dropped silently: no "Unresolved directive" text is
    // inserted and no warning is emitted.
    let handler = InlineFileHandler::from_pairs([("other.adoc", "unused")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_primary_file_name("my-document.adoc")
        .with_include_file_handler(handler)
        .parse("Before.\n\ninclude::content.adoc[opts=optional]\n\nAfter.");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Before.", "After."]);
    assert_eq!(doc.warnings().count(), 0);
}

#[test]
fn attribute_reference_in_include_path() {
    verifies!(
        r#"
If you store your AsciiDoc files in nested folders at different levels, relative file paths can quickly become awkward and inflexible.
A common pattern to help here is to define the paths in attributes defined in the header, then prefix all include paths with a reference to one of these attributes:

------
:includedir: _includes
:sourcedir: ../src/main/java

\include::{includedir}/fragment1.adoc[]

[source,java]
----
\include::{sourcedir}/org/asciidoctor/Asciidoctor.java[]
----
------

"#
    );

    // An attribute reference in the include target is resolved before the target
    // is looked up.
    let handler = InlineFileHandler::from_pairs([("_includes/fragment1.adoc", "Fragment one.")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse(":includedir: _includes\n\ninclude::{includedir}/fragment1.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Fragment one."]);
}

non_normative!(
    r#"
Keep in mind that no matter how Asciidoctor resolves the path to the file, access to that file is limited by the safe mode setting under which Asciidoctor is run.
If a path violates the security restrictions, it may be truncated.

"#
);

#[test]
fn merges_any_text_file_with_normalization() {
    verifies!(
        r#"
[#include-nonasciidoc]
== AsciiDoc vs non-AsciiDoc files

The include directive performs a simple file merge, so it works with any text file.
// NOTE this point about normalization should probably be moved to an earlier section
The content of all included content goes through some form of normalization.

"#
    );

    // The directive merges any text file's content, not just AsciiDoc: here a CSV
    // fragment is included verbatim. The content is normalized as it is merged —
    // the CRLF line ending becomes a Unix line feed.
    let handler = InlineFileHandler::from_pairs([("results.csv", "Year,Total\r\n2016,1234")]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::results.csv[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Year,Total\n2016,1234"]);
}

// Character-encoding handling (the encoding attribute and BOM detection) is out
// of scope for this crate; included content is assumed to already be UTF-8.
non_normative!(
    r#"
The content of each include file is encoded to UTF-8.
If the encoding attribute is specified on the include directive, the content is reencoded from that encoding to UTF-8.
If the encoding attribute is not specified, the processor will look for the presence of a BOM and reencode the content from that encoding to UTF-8 accordingly.
If neither of those conditions are met, the encoding is forced to UTF-8.

"#
);

#[test]
fn asciidoc_file_gets_additional_processing() {
    verifies!(
        r#"
If the file is recognized as an AsciiDoc file (i.e., it has one of the following extensions: `.asciidoc`, `.adoc`, `.ad`, `.asc`, or `.txt`) additional normalization and processing is performed.
"#
    );

    // An include whose target is recognized as AsciiDoc (by its `.adoc`
    // extension) is run through the preprocessor, so an include directive nested
    // within it is itself expanded.
    let handler = InlineFileHandler::from_pairs([
        ("chapter.adoc", "Chapter intro.\n\ninclude::fragment.adoc[]"),
        ("fragment.adoc", "Fragment body."),
    ]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::chapter.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Chapter intro.", "Fragment body."]);
}

// Trailing-whitespace stripping is not implemented, so the normalization detail
// below remains non-normative. (The `includes` bullet is covered by the
// nested-include test that follows; the conditionals bullet is verified below,
// with full coverage in the dedicated `conditionals`/`ifdef_ifndef`/`ifeval`
// spec files.)
non_normative!(
    r#"
First, all trailing whitespace and endlines are removed from each line and replaced with a Unix line feed.
This normalization is important to how an AsciiDoc processor works.
Next, the AsciiDoc processor runs the preprocessor on the lines, looking for and interpreting the following directives:

* includes
"#
);

#[test]
fn preprocessor_interprets_conditionals() {
    verifies!(
        r#"
* preprocessor conditionals (e.g., `ifdef`)
"#
    );

    // The preprocessor interprets conditional directives: an `ifdef` whose
    // attribute is set includes its enclosed content.
    let doc = Parser::default().parse(":flag:\n\nifdef::flag[]\nShown.\nendif::[]");
    assert_eq!(rendered_paragraphs(&doc), vec!["Shown."]);
}

non_normative!(
    r#"
//* front matter (if enabled)

"#
);

#[test]
fn nested_includes() {
    verifies!(
        r#"
Running the preprocessor on the included content allows includes to be nested, thus provides lot of flexibility in constructing radically different documents with a single primary document and a few command line attributes.

"#
    );

    // The preprocessor runs on included content, so an include inside an included
    // file is itself resolved (nested includes).
    let handler = InlineFileHandler::from_pairs([
        ("outer.adoc", "Outer top.\n\ninclude::inner.adoc[]"),
        ("inner.adoc", "Inner content."),
    ]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::outer.adoc[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["Outer top.", "Inner content."]);
}

non_normative!(
    r#"
Including non-AsciiDoc files is normally done to merge output from other programs or populate table data:

----
.2016 Sales Results
,===
\include::sales/2016/results.csv[]
,===
----

"#
);

#[test]
fn non_asciidoc_file_inserted_verbatim() {
    verifies!(
        r#"
In this case, the include directive does not do any processing of AsciiDoc directives.
The content is inserted as is (after being normalized).

"#
    );

    // A non-AsciiDoc include (here a `.csv` file) is merged verbatim: an include
    // directive nested within it is left as literal text, never expanded.
    let handler = InlineFileHandler::from_pairs([
        ("results.csv", "year,total\ninclude::fragment.adoc[]"),
        ("fragment.adoc", "SHOULD NOT APPEAR"),
    ]);

    let doc = Parser::default()
        .with_safe_mode(SafeMode::Server)
        .with_include_file_handler(handler)
        .parse("include::results.csv[]");

    let paras = rendered_paragraphs(&doc);
    let paras: Vec<&str> = paras.iter().map(|s| s.as_str()).collect();

    assert_eq!(paras, vec!["year,total\ninclude::fragment.adoc[]"]);
}

non_normative!(
    r#"
////
CAUTION: You *can* put AsciiDoc content in a non-AsciiDoc file.
Its content will still be processed as AsciiDoc, but any include statements will be ignored, and therefore cause errors later in processing.
It is likely to cause confusion, so best avoided.
////
"#
);