diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 3d6967f9cd816ec8e7e10650305de5706e031480..64b1e7ffac9e6ab810c4a7d0b68cc7520fbd2ddd 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,7 @@ For a detailed view of what's changed, refer to the {url-repo}/commits[commit hi * preserve role on page title when adding as section title in assembly * add implicit author line to assembly if author is defined on top-level page (#93) * allow doctype of assembly to be set using `assembly.doctype` config key (instead of the `doctype` AsciiDoc attribute) +* make separator used to scope IDs configurable using `assembly.id_separator` config key (#94) === Changed diff --git a/docs/assembler/modules/ROOT/pages/configure-assembly.adoc b/docs/assembler/modules/ROOT/pages/configure-assembly.adoc index 8526ce188f98a571cb4148f45bd4a2c23cc42fff..87b66e273d03dc47712a4dd3016fa81256b145cd 100644 --- a/docs/assembler/modules/ROOT/pages/configure-assembly.adoc +++ b/docs/assembler/modules/ROOT/pages/configure-assembly.adoc @@ -40,6 +40,12 @@ Acceptable keys and their default values are listed in the table below. |article, book, manpage |Sets the AsciiDoc doctype of the assembly. +|<> +|: +|Character +|Sets the character Assembler uses as a separator when scoping IDs. +Do not use underscore (`_`). + |<> | |String @@ -78,7 +84,7 @@ If `root_level` isn't set, the extension will automatically assign the value `0` The `insert_start_page` key controls whether the component version start page is implicitly inserted at the top of the navigation model. -The `insert_start_page` key in an optional key that can be set in [.path]_antora-assembler.yml_. +The `insert_start_page` key is an optional key that can be set in [.path]_antora-assembler.yml_. It accepts a Boolean value. `true`:: _Default_. @@ -349,7 +355,7 @@ The child navigation entry for the _willow-creek.adoc_ page becomes a sibling of The `doctype` key controls the AsciiDoc doctype of the assembly. -The `doctype` key in an optional key that can be set in [.path]_antora-assembler.yml_. +The `doctype` key is an optional key that can be set in [.path]_antora-assembler.yml_. It accepts a valid doctype value, `book`, `article`, or `manpage`. The default value is `book`. @@ -363,6 +369,31 @@ assembly: This key should be used when you do not want the assembly to contain parts or chapters. Assembler automatically adjusts section levels when the value is anything other than book. +[#id-separator-key] +== id_separator + +The `id_separator` key controls the character Assembler uses as a separator when scoping IDs. + +The `id_separator` key is an optional key that can be set in [.path]_antora-assembler.yml_. +It accepts a single character. +The default value is `:`. + +.antora-assembler.yml +[,yaml] +---- +assembly: + id_separator: '-' +---- + +Assembler uses `:` as a separator by default, which is not a valid ID character in XML. +Thus, when converting to an XML format such as DocBook, you may want to customize this value. +The recommended value is `-`. +Do not use underscore (`_`) as this interferes with conversion. + +The separator is repeated three times between the page ID and the section ID (e.g., `page:::section`) and used as is between each resource ID coordinate (e.g., `component:module:page`). +When the value is `-`, the separator is repeated four times betweeen each resource ID coordinate (e.g., `module----page`). +This repetition is used to avoid conflicts with the endash replacement in AsciiDoc. + [#profile-key] == profile diff --git a/packages/assembler/lib/produce-assembly-file.js b/packages/assembler/lib/produce-assembly-file.js index 3102c93d545947d0d5f346bfe010629191aa8466..82ff24c5b04a87d3de5016a6efb773c4115c3cf7 100644 --- a/packages/assembler/lib/produce-assembly-file.js +++ b/packages/assembler/lib/produce-assembly-file.js @@ -116,6 +116,9 @@ function mergeAsciiDoc ( if (!val || val === '/') return '' return val.charAt(val.length - 1) === '/' ? val.slice(0, val.length - 1) : val })(asciidocConfig.attributes['site-url']) + const idSeparator = (assemblyModel.idSeparator || '').charAt() || ':' + const idScopeSeparator = idSeparator.repeat(3) + const idCoordinateSeparator = idSeparator === '-' ? '----' : idSeparator // FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently let page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined if (page && pagesInOutline.assembled.pages.has(page)) page = undefined @@ -154,13 +157,13 @@ function mergeAsciiDoc ( let idScope = docnameForId let idPrefix if (qualifyId) { - idScope = component + ':' + (module_ === 'ROOT' ? ':' : module_ + ':') + idScope + idScope = [component, module_ === 'ROOT' ? '' : module_, idScope].join(idCoordinateSeparator) } else if (module_ !== 'ROOT') { - idScope = module_ + ':' + idScope + idScope = module_ + idCoordinateSeparator + idScope } else if (ReservedIdNames.includes(docnameForId)) { - idScope = idPrefix = idScope + ':::' + idScope = idPrefix = idScope + idScopeSeparator } - idPrefix ??= idScope + ':::' + idPrefix ??= idScope + idScopeSeparator let pageFragment = '' let pageRoles = '' let pageStyle = doc.getAttribute('assembly-style', '') @@ -384,19 +387,20 @@ function mergeAsciiDoc ( targetModule = module_ } if (pagePart.startsWith('./')) pagePart = topicPrefix + pagePart.slice(2) - if (targetModule !== 'ROOT') pagePart = `${targetModule}:${pagePart}` - if (!(targetPage = pagesInOutline.get(pagePart))) { - if (siteUrl && (targetPage = contentCatalog.resolvePage(pagePart, page.src)) && targetPage.out) { + const pageResourceRef = targetModule === 'ROOT' ? pagePart : `${targetModule}:${pagePart}` + if (!(targetPage = pagesInOutline.get(pageResourceRef))) { + if (siteUrl && (targetPage = contentCatalog.resolvePage(pageResourceRef, page.src)) && targetPage.out) { text ||= targetPage.asciidoc?.xreftext || target return `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]` } // TODO: handle unresolved page better return m } + if (targetModule !== 'ROOT') pagePart = `${targetModule}${idCoordinateSeparator}${pagePart}` pagePart = pagePart.replace(/\.adoc$/, '').replace(/[/.]/g, '-') const refid = fragment - ? `${pagePart}:::${fragment}` - : pagePart + (ReservedIdNames.includes(pagePart) ? ':::' : '') + ? `${pagePart}${idScopeSeparator}${fragment}` + : pagePart + (ReservedIdNames.includes(pagePart) ? idScopeSeparator : '') return `<<${refid}${text && text !== targetPage.title ? ',' + text.replace(/\\]/g, ']') : ''}>>` }) } diff --git a/packages/assembler/lib/produce-assembly-files.js b/packages/assembler/lib/produce-assembly-files.js index d867531231b5fdddd694743c10aed3ee2f662943..816931b2650357bff82ada201f38ec49cb04590d 100644 --- a/packages/assembler/lib/produce-assembly-files.js +++ b/packages/assembler/lib/produce-assembly-files.js @@ -11,6 +11,7 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re const { asciidoc: assemblerAsciiDocConfig, assembly: assemblyConfig } = assemblerConfig resolveAssemblyModel ??= (componentVersion) => ({ doctype: assemblyConfig.doctype, + idSeparator: assemblyConfig.idSeparator, insertStartPage: assemblyConfig.insertStartPage, rootLevel: assemblyConfig.rootLevel, sectionMergeStrategy: assemblyConfig.sectionMergeStrategy, diff --git a/packages/assembler/test/produce-assembly-files-test.js b/packages/assembler/test/produce-assembly-files-test.js index 191b57b1cc1ce61253da20129ace50162334c30d..e2035686c78361d7cc11b216acab8c4144176557 100644 --- a/packages/assembler/test/produce-assembly-files-test.js +++ b/packages/assembler/test/produce-assembly-files-test.js @@ -171,6 +171,10 @@ describe('produceAssemblyFiles()', () => { await runScenario('rewrite-relative-page-links', __dirname) }) + it('should allow id separator to be specified', async () => { + await runScenario('alternate-id-separator', __dirname) + }) + it('should skip commented lines', async () => { await runScenario('skip-commented-lines', __dirname) }) diff --git a/packages/assembler/test/scenarios/alternate-id-separator/data.yml b/packages/assembler/test/scenarios/alternate-id-separator/data.yml new file mode 100644 index 0000000000000000000000000000000000000000..4b25f69ce0033524410adfae55cd2ed6a6fef7a5 --- /dev/null +++ b/packages/assembler/test/scenarios/alternate-id-separator/data.yml @@ -0,0 +1,49 @@ +name: the-component +version: '1.0' +title: The Component +navigation: + items: + - content: xref:the-page.adoc[] + - content: xref:other-module:the-page.adoc[] + - content: xref:other-component::the-page.adoc[] + - content: xref:other-component:other-module:the-page.adoc[] +files: +- relative: the-page.adoc + contents: |- + = The Page + + [#the-section] + == The Section + + Also see xref:other-module:the-page.adoc#the-section[The Section] in xref:other-module:the-page.adoc[]. +- relative: the-page.adoc + module: other-module + contents: |- + = The Page in Other Module + + [#the-section] + == The Section + + content +- relative: the-page.adoc + component: other-component + contents: |- + = Other Page in Other Component + + [#the-section] + == The Section + + content +- relative: the-page.adoc + component: other-component + module: other-module + contents: |- + = Other Page in Other Module in Other Component + + [#the-section] + == The Section + + content +assembler: + assembly: + idSeparator: '-' diff --git a/packages/assembler/test/scenarios/alternate-id-separator/expects/index.adoc b/packages/assembler/test/scenarios/alternate-id-separator/expects/index.adoc new file mode 100644 index 0000000000000000000000000000000000000000..360a1c05b62d1cca9d76d280e0db3d84fe012910 --- /dev/null +++ b/packages/assembler/test/scenarios/alternate-id-separator/expects/index.adoc @@ -0,0 +1,83 @@ += The Component +:revnumber: 1.0 +:doctype: book +:underscore: _ +:page-component-name: the-component +:page-component-version: 1.0 +:page-version: {page-component-version} +:page-component-display-version: 1.0 +:page-component-title: The Component + +:docname: the-page +:page-module: ROOT +:page-relative-src-path: the-page.adoc +:page-origin-url: https://github.com/acme/the-component +:page-origin-start-path: +:page-origin-refname: v1.0 +:page-origin-reftype: branch +:page-origin-refhash: a00000000000000000000000000000000000000z +[#the-page] +== The Page + +[discrete#the-page---the-section] +=== The Section + +Also see <> in <>. + +:docname: the-page +:page-module: other-module +:page-relative-src-path: the-page.adoc +:page-origin-url: https://github.com/acme/the-component +:page-origin-start-path: +:page-origin-refname: v1.0 +:page-origin-reftype: branch +:page-origin-refhash: a00000000000000000000000000000000000000z +[#other-module----the-page] +== The Page in Other Module + +[discrete#other-module----the-page---the-section] +=== The Section + +content + +:docname: the-page +:page-component-name: other-component +:page-component-version: +:page-version: {page-component-version} +:page-component-display-version: default +:page-component-title: Other Component +:page-module: ROOT +:page-relative-src-path: the-page.adoc +:page-origin-url: https://github.com/acme/other-component +:page-origin-start-path: +:page-origin-refname: main +:page-origin-reftype: branch +:page-origin-refhash: b00000000000000000000000000000000000000y +[#other-component--------the-page] +== Other Page in Other Component + +[discrete#other-component--------the-page---the-section] +=== The Section + +content + +:docname: the-page +:page-component-name: other-component +:page-component-version: +:page-version: {page-component-version} +:page-component-display-version: default +:page-component-title: Other Component +:page-module: other-module +:page-relative-src-path: the-page.adoc +:page-origin-url: https://github.com/acme/other-component +:page-origin-start-path: +:page-origin-refname: main +:page-origin-reftype: branch +:page-origin-refhash: b00000000000000000000000000000000000000y +[#other-component----other-module----the-page] +== Other Page in Other Module in Other Component + +[discrete#other-component----other-module----the-page---the-section] +=== The Section + +content