use crate::{document::RefType, tests::prelude::*};
track_file!("ref/asciidoc-lang/docs/modules/sections/pages/custom-ids.adoc");
non_normative!(
r#"
= Assign Custom IDs and Reference Text
:url-adoc-a2x: https://aerostitch.github.io/misc/asciidoc/asciidoc-title_uft8.html
You can assign a custom ID and optional reference text (i.e., label) to a section (see xref:attributes:id.adoc#anchor[anchor]).
The custom ID is used in place of the autogenerated ID.
This can be useful when you want to define a stable anchor for linking to a section using a cross reference.
The reference text is used when referencing the ID without specifying explicit text.
This is a good way to centrally manage the automatic reference text that is used to refer to a section.
"#
);
#[test]
fn ex_custom_id() {
verifies!(
r#"
Here's an example of a section with a custom ID:
[source]
----
include::example$section.adoc[tag=with-anchor-shorthand]
----
"#
);
let doc = Parser::default().parse("[#tigers-subspecies]\n=== Subspecies of Tiger");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 2,
section_title: Content {
original: Span {
data: "Subspecies of Tiger",
line: 2,
col: 5,
offset: 25,
},
rendered: "Subspecies of Tiger",
},
blocks: &[],
source: Span {
data: "[#tigers-subspecies]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: Some(Attrlist {
attributes: &[ElementAttribute {
name: None,
value: "#tigers-subspecies",
shorthand_items: &["#tigers-subspecies"],
},],
anchor: None,
source: Span {
data: "#tigers-subspecies",
line: 1,
col: 2,
offset: 1,
},
},),
section_type: SectionType::Normal,
section_id: None,
caption: None,
section_number: None,
},),],
source: Span {
data: "[#tigers-subspecies]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([(
"tigers-subspecies",
RefEntry {
id: "tigers-subspecies",
reftext: Some("Subspecies of Tiger",),
ref_type: RefType::Section,
},
)]),
reftext_to_id: HashMap::from([("Subspecies of Tiger", "tigers-subspecies",)]),
},
}
);
}
#[test]
fn ex_custom_id_with_reftext() {
verifies!(
r#"
Here's an example of a section with a custom ID and reference text:
[source]
----
include::example$section.adoc[tag=with-anchor-and-reftext-shorthand]
----
"#
);
let doc =
Parser::default().parse("[#tigers-subspecies,reftext=Subspecies]\n=== Subspecies of Tiger");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 2,
section_title: Content {
original: Span {
data: "Subspecies of Tiger",
line: 2,
col: 5,
offset: 44,
},
rendered: "Subspecies of Tiger",
},
blocks: &[],
source: Span {
data: "[#tigers-subspecies,reftext=Subspecies]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: Some(Attrlist {
attributes: &[
ElementAttribute {
name: None,
value: "#tigers-subspecies",
shorthand_items: &["#tigers-subspecies",],
},
ElementAttribute {
name: Some("reftext",),
value: "Subspecies",
shorthand_items: &[],
},
],
anchor: None,
source: Span {
data: "#tigers-subspecies,reftext=Subspecies",
line: 1,
col: 2,
offset: 1,
},
},),
section_type: SectionType::Normal,
section_id: None,
caption: None,
section_number: None,
},),],
source: Span {
data: "[#tigers-subspecies,reftext=Subspecies]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([(
"tigers-subspecies",
RefEntry {
id: "tigers-subspecies",
reftext: Some("Subspecies",),
ref_type: RefType::Section,
}
),]),
reftext_to_id: HashMap::from([("Subspecies", "tigers-subspecies"),]),
},
}
);
}
#[test]
fn ex_custom_id_with_reftext_quoted() {
verifies!(
r#"
IMPORTANT: The value of the reftext attribute must be quoted if it contains spaces or commas.
"#
);
let doc = Parser::default()
.parse("[#tigers-subspecies,reftext=\"Subspecies, etc.\"]\n=== Subspecies of Tiger");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 2,
section_title: Content {
original: Span {
data: "Subspecies of Tiger",
line: 2,
col: 5,
offset: 52,
},
rendered: "Subspecies of Tiger",
},
blocks: &[],
source: Span {
data: "[#tigers-subspecies,reftext=\"Subspecies, etc.\"]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: Some(Attrlist {
attributes: &[
ElementAttribute {
name: None,
value: "#tigers-subspecies",
shorthand_items: &["#tigers-subspecies"],
},
ElementAttribute {
name: Some("reftext",),
value: "Subspecies, etc.",
shorthand_items: &[],
},
],
anchor: None,
source: Span {
data: "#tigers-subspecies,reftext=\"Subspecies, etc.\"",
line: 1,
col: 2,
offset: 1,
},
},),
section_type: SectionType::Normal,
section_id: None,
caption: None,
section_number: None,
},),],
source: Span {
data: "[#tigers-subspecies,reftext=\"Subspecies, etc.\"]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([(
"tigers-subspecies",
RefEntry {
id: "tigers-subspecies",
reftext: Some("Subspecies, etc.",),
ref_type: RefType::Section,
}
),]),
reftext_to_id: HashMap::from([("Subspecies, etc.", "tigers-subspecies"),]),
},
}
);
}
#[test]
fn ex_block_anchor_syntax_with_reftext() {
verifies!(
r#"
The ID and reference text can also be defined using the block anchor syntax:
[source]
----
include::example$section.adoc[tag=with-anchor-and-reftext]
----
"#
);
let doc = Parser::default().parse("[[tigers-subspecies,Subspecies]]\n=== Subspecies of Tiger");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 2,
section_title: Content {
original: Span {
data: "Subspecies of Tiger",
line: 2,
col: 5,
offset: 37,
},
rendered: "Subspecies of Tiger",
},
blocks: &[],
source: Span {
data: "[[tigers-subspecies,Subspecies]]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: Some(Span {
data: "tigers-subspecies",
line: 1,
col: 3,
offset: 2,
},),
anchor_reftext: Some(Span {
data: "Subspecies",
line: 1,
col: 21,
offset: 20,
},),
attrlist: None,
section_type: SectionType::Normal,
section_id: None,
caption: None,
section_number: None,
},),],
source: Span {
data: "[[tigers-subspecies,Subspecies]]\n=== Subspecies of Tiger",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([(
"tigers-subspecies",
RefEntry {
id: "tigers-subspecies",
reftext: Some("Subspecies of Tiger",),
ref_type: RefType::Section,
}
),]),
reftext_to_id: HashMap::from([("Subspecies of Tiger", "tigers-subspecies"),]),
},
}
);
}
non_normative!(
r#"
When using the block anchor syntax, the ID must conform to the https://www.w3.org/TR/REC-xml/#NT-Name[XML Name] rules, which means the ID must start with a letter, an underscore, or a colon.
IMPORTANT: AsciiDoc allows all valid UTF-8 characters to be used in section IDs.
If you're generating a PDF from AsciiDoc using a2x and dblatex, see {url-adoc-a2x}[Using UTF-8 titles with a2x] to learn about the required `latex.encoding=utf8` switch to activate this portability.
"#
);
#[test]
fn assign_auxiliary_ids_1() {
verifies!(
r#"
== Assign auxiliary IDs
A section title can only have a single primary ID.
However, it's possible to register auxiliary IDs on a section title for referencing from the URL using inline anchors.
This feature works regardless of whether you assign an explicit (primary) ID.
CAUTION: If possible, you should avoid adding inline anchors on a section title.
However, if you need to be able to link to that section from a URL using alternate fragment identifiers, this is what you need to use.
Here's how to register auxiliary IDs using inline anchors when using an autogenerated ID:
.Register auxiliary IDs at the beginning of the section title
[source]
----
== [[secondary-id]][[tertiary-id]]Section Title
----
"#
);
let doc = Parser::default().parse("== [[secondary-id]][[tertiary-id]]Section Title");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 1,
section_title: Content {
original: Span {
data: "[[secondary-id]][[tertiary-id]]Section Title",
line: 1,
col: 4,
offset: 3,
},
rendered: "<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
},
blocks: &[],
source: Span {
data: "== [[secondary-id]][[tertiary-id]]Section Title",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: None,
section_type: SectionType::Normal,
section_id: Some("_section_title",),
caption: None,
section_number: None,
},),],
source: Span {
data: "== [[secondary-id]][[tertiary-id]]Section Title",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([
(
"_section_title",
RefEntry {
id: "_section_title",
reftext: Some(
"<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
),
ref_type: RefType::Section,
},
),
(
"secondary-id",
RefEntry {
id: "secondary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
(
"tertiary-id",
RefEntry {
id: "tertiary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
]),
reftext_to_id: HashMap::from([(
"<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
"_section_title"
),]),
},
},
);
}
#[test]
fn assign_auxiliary_ids_2() {
verifies!(
r#"
.Register auxiliary IDs at the end of the section title
[source]
----
== Section Title[[secondary-id]][[tertiary-id]]
----
"#
);
let doc = Parser::default().parse("== Section Title[[secondary-id]][[tertiary-id]]");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 1,
section_title: Content {
original: Span {
data: "Section Title[[secondary-id]][[tertiary-id]]",
line: 1,
col: 4,
offset: 3,
},
rendered: "Section Title<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>",
},
blocks: &[],
source: Span {
data: "== Section Title[[secondary-id]][[tertiary-id]]",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: None,
section_type: SectionType::Normal,
section_id: Some("_section_title",),
caption: None,
section_number: None,
},),],
source: Span {
data: "== Section Title[[secondary-id]][[tertiary-id]]",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([
(
"_section_title",
RefEntry {
id: "_section_title",
reftext: Some(
"Section Title<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>",
),
ref_type: RefType::Section,
},
),
(
"secondary-id",
RefEntry {
id: "secondary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
(
"tertiary-id",
RefEntry {
id: "tertiary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
]),
reftext_to_id: HashMap::from([(
"Section Title<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>",
"_section_title"
),]),
}
}
);
}
non_normative!(
r#"
Where you place the inline anchor is where the anchor will end up in the output.
The beginning is the preferred location.
"#
);
#[test]
fn auxiliary_ids_() {
verifies!(
r#"
These additional anchor points don't interfere with the declaration of the primary ID, as shown in the next example.
.Register auxiliary IDs on a section title with an explicit ID
[source]
----
[#primary-id]
== [[secondary-id]][[tertiary-id]]Section Title
----
"#
);
let doc =
Parser::default().parse("[#primary-id]\n== [[secondary-id]][[tertiary-id]]Section Title");
assert_eq!(
doc,
Document {
header: Header {
title_source: None,
title: None,
attributes: &[],
author_line: None,
revision_line: None,
comments: &[],
source: Span {
data: "",
line: 1,
col: 1,
offset: 0,
},
},
blocks: &[Block::Section(SectionBlock {
level: 1,
section_title: Content {
original: Span {
data: "[[secondary-id]][[tertiary-id]]Section Title",
line: 2,
col: 4,
offset: 17,
},
rendered: "<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
},
blocks: &[],
source: Span {
data: "[#primary-id]\n== [[secondary-id]][[tertiary-id]]Section Title",
line: 1,
col: 1,
offset: 0,
},
title_source: None,
title: None,
anchor: None,
anchor_reftext: None,
attrlist: Some(Attrlist {
attributes: &[ElementAttribute {
name: None,
value: "#primary-id",
shorthand_items: &["#primary-id"]
},],
anchor: None,
source: Span {
data: "#primary-id",
line: 1,
col: 2,
offset: 1,
},
},),
section_type: SectionType::Normal,
section_id: None,
caption: None,
section_number: None,
},),],
source: Span {
data: "[#primary-id]\n== [[secondary-id]][[tertiary-id]]Section Title",
line: 1,
col: 1,
offset: 0,
},
warnings: &[],
source_map: SourceMap(&[]),
catalog: Catalog {
refs: HashMap::from([
(
"primary-id",
RefEntry {
id: "primary-id",
reftext: Some(
"<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
),
ref_type: RefType::Section,
},
),
(
"secondary-id",
RefEntry {
id: "secondary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
(
"tertiary-id",
RefEntry {
id: "tertiary-id",
reftext: None,
ref_type: RefType::Anchor,
},
),
]),
reftext_to_id: HashMap::from([(
"<a id=\"secondary-id\"></a><a id=\"tertiary-id\"></a>Section Title",
"primary-id"
),]),
},
}
);
}
non_normative!(
r#"
IMPORTANT: These auxiliary IDs are not registered with the referencing system.
That means they cannot be used for referencing the section title within the document.
They are only intended for assigning auxiliary fragment identifiers to the section title so it can be referenced the from the URL using a URL fragment (aka deep linking).
Only the primary ID can be used for referencing the section title within the document.
"#
);